├── language └── en │ ├── iso.txt │ └── info_acp_qi.php ├── composer.phar ├── style ├── phpinfo_body.twig ├── docs_body.twig ├── overall_footer.twig ├── sidebar_docs.twig ├── sidebar_phpinfo.twig ├── error.twig ├── sidebar_boards.twig ├── navbar.twig ├── overall_header.twig ├── sidebar_settings.twig └── assets │ ├── css │ └── qi_style.css │ ├── js │ └── scripts.js │ └── img │ ├── logo_small_white.svg │ └── logo_medium_cosmos.svg ├── includes ├── qi_constants.php ├── install_install_qi.php ├── db │ ├── 30x │ │ ├── postgres.php │ │ ├── mssql.php │ │ ├── mysqli.php │ │ ├── mysql.php │ │ └── sqlite.php │ ├── postgres.php │ ├── mssql.php │ ├── mysqli.php │ ├── mysql.php │ ├── sqlite3.php │ └── sqlite.php ├── qi_module.php ├── default_settings.json ├── functions_forum_create.php ├── class_31_styles.php ├── qi_file.php ├── class_30_styles.php ├── twig.php ├── functions_install.php ├── qi_version_helper.php └── settings.php ├── composer.json ├── scss └── qi_bootstrap.scss ├── modules ├── qi_docs.php ├── qi_main.php ├── qi_phpinfo.php ├── qi_manage.php └── qi_settings.php ├── package.json ├── .github └── CONTRIBUTING.md ├── README.md ├── index.php ├── composer.lock └── license.txt /language/en/iso.txt: -------------------------------------------------------------------------------- 1 | British English 2 | British English 3 | phpBB Limited -------------------------------------------------------------------------------- /composer.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phpBB/quickinstall/master/composer.phar -------------------------------------------------------------------------------- /style/phpinfo_body.twig: -------------------------------------------------------------------------------- 1 | {% include 'overall_header.twig' %} 2 | 3 |

{{ lang('PHPINFO_TITLE') }}

4 | {{ lang('PHPINFO_EXPLAIN') }} 5 | 6 |
7 | {{ PHPINFO }} 8 |
9 | 10 | {% include 'overall_footer.twig' %} 11 | -------------------------------------------------------------------------------- /includes/qi_constants.php: -------------------------------------------------------------------------------- 1 | 'Installed by phpBB Quickinstall version %s', 27 | )); 28 | -------------------------------------------------------------------------------- /style/docs_body.twig: -------------------------------------------------------------------------------- 1 | {% include 'overall_header.twig' %} 2 |
3 | {{ DOC_BODY }} 4 |
5 |
6 |

{{ lang('CHANGELOG') }}

7 | {% for change in history %} 8 |
9 |

{{ change.CHANGES_SINCE }}

10 | 15 |
16 | {% endfor %} 17 |
18 | {% include 'overall_footer.twig' %} 19 | -------------------------------------------------------------------------------- /includes/install_install_qi.php: -------------------------------------------------------------------------------- 1 | data = $data; 28 | } 29 | 30 | public function get_submitted_data() 31 | { 32 | return $this->data; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /style/overall_footer.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /style/sidebar_docs.twig: -------------------------------------------------------------------------------- 1 |

{{ lang('DOCS_LONG') }}

2 | 9 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phpbb/quickinstall", 3 | "description": "QuickInstall is a developer tool used to create multiple phpBB3 installations.", 4 | "homepage": "https://www.phpbb.com/customise/db/official_tool/phpbb3_quickinstall", 5 | "version": "1.6.15", 6 | "license": "GPL-2.0-only", 7 | "authors": [ 8 | { 9 | "name": "Matt Friedman", 10 | "role": "Extensions Development Team Lead" 11 | }, 12 | { 13 | "name": "Igor Wiedler (igorw)", 14 | "role": "Created by" 15 | }, 16 | { 17 | "name": "Jari Kanerva (tumba25)", 18 | "role": "Maintained from March 2010 to March 2015" 19 | } 20 | ], 21 | "require": { 22 | "php": ">=5.4", 23 | "twig/twig": "^1.0", 24 | "erusev/parsedown": "^1.7", 25 | "ext-json": "*" 26 | }, 27 | "config": { 28 | "platform": { 29 | "php": "5.4.7" 30 | } 31 | }, 32 | "require-dev": { 33 | "phing/phing": "^2.16" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /style/sidebar_phpinfo.twig: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 | 26 | -------------------------------------------------------------------------------- /includes/db/30x/postgres.php: -------------------------------------------------------------------------------- 1 | modules_path = (string) $modules_path; 23 | $this->modules_prefix = (string) $modules_prefix; 24 | } 25 | 26 | public function load($module, $default) 27 | { 28 | global $phpEx; 29 | 30 | // just some security (thanks lordlebrand) 31 | $module = basename($module); 32 | 33 | if (!file_exists($this->modules_path . $this->modules_prefix . $module . '.' . $phpEx)) 34 | { 35 | $module = $default; 36 | } 37 | 38 | if (false === @include($this->modules_path . $this->modules_prefix . $module . '.' . $phpEx)) 39 | { 40 | trigger_error(qi::lang('NO_MODULE', $module), E_USER_ERROR); 41 | } 42 | 43 | $class_name = $this->modules_prefix . $module; 44 | $module = new $class_name(); 45 | return $module->run(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /includes/db/30x/mssql.php: -------------------------------------------------------------------------------- 1 | persistency = $persistency; 30 | $this->user = $sqluser; 31 | $this->server = $sqlserver . (($port) ? ':' . $port : ''); 32 | $this->dbname = $database; 33 | 34 | @ini_set('mssql.charset', 'UTF-8'); 35 | @ini_set('mssql.textlimit', 2147483647); 36 | @ini_set('mssql.textsize', 2147483647); 37 | 38 | $this->db_connect_id = ($this->persistency) ? @mssql_pconnect($this->server, $this->user, $sqlpassword, $new_link) : @mssql_connect($this->server, $this->user, $sqlpassword, $new_link); 39 | 40 | return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); 41 | } 42 | 43 | /** 44 | * Select a database 45 | * 46 | * @param string $dbname 47 | */ 48 | function sql_select_db($dbname) 49 | { 50 | return @mssql_select_db($dbname, $this->db_connect_id); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /includes/db/30x/mysqli.php: -------------------------------------------------------------------------------- 1 | persistency = $persistency; 30 | $this->user = $sqluser; 31 | $this->server = $sqlserver; 32 | $this->dbname = $database; 33 | $port = (!$port) ? NULL : $port; 34 | 35 | $this->sql_layer = 'mysql_41'; 36 | 37 | // Persistant connections not supported by the mysqli extension? 38 | $this->db_connect_id = @mysqli_connect($this->server, $this->user, $sqlpassword, null, $port); 39 | 40 | if ($this->db_connect_id) 41 | { 42 | @mysqli_query($this->db_connect_id, "SET NAMES 'utf8'"); 43 | return $this->db_connect_id; 44 | } 45 | 46 | return $this->sql_error(''); 47 | } 48 | 49 | /** 50 | * Select a database 51 | * 52 | * @param string $dbname 53 | */ 54 | function sql_select_db($dbname) 55 | { 56 | return @mysqli_select_db($this->db_connect_id, $dbname); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /includes/db/mssql.php: -------------------------------------------------------------------------------- 1 | persistency = $persistency; 30 | $this->user = $sqluser; 31 | $this->server = $sqlserver . (($port) ? ':' . $port : ''); 32 | $this->dbname = $database; 33 | 34 | @ini_set('mssql.charset', 'UTF-8'); 35 | @ini_set('mssql.textlimit', 2147483647); 36 | @ini_set('mssql.textsize', 2147483647); 37 | 38 | $this->db_connect_id = ($this->persistency) ? @mssql_pconnect($this->server, $this->user, $sqlpassword, $new_link) : @mssql_connect($this->server, $this->user, $sqlpassword, $new_link); 39 | 40 | return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); 41 | } 42 | 43 | /** 44 | * Select a database 45 | * 46 | * @param string $dbname 47 | */ 48 | public function sql_select_db($dbname) 49 | { 50 | return @mssql_select_db($dbname, $this->db_connect_id); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /scss/qi_bootstrap.scss: -------------------------------------------------------------------------------- 1 | // Define QI theme variables for Bootstrap 2 | 3 | // Colors for QI 4 | $primary: #009bdf; 5 | $secondary: #f5f7fa; 6 | 7 | // Fonts for QI 8 | $font-family-sans-serif: "Open Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 9 | $font-size-base: 0.875rem; 10 | 11 | // Tweak BS defaults for QI 12 | $link-decoration: none; 13 | $legend-margin-bottom: 1.25rem; 14 | $card-border-radius: .5rem; 15 | 16 | // Theme stuff for QI 17 | $enable-gradients: true; 18 | 19 | // Import Bootstrap (do not change or move this line) 20 | @import "../node_modules/bootstrap/scss/bootstrap.scss"; 21 | 22 | // Extend Bootstrap with new Callout component 23 | .callout { 24 | padding: $spacer 1.25rem; 25 | margin: $spacer 0; 26 | border-radius: $border-radius; 27 | border: $border-width solid $gray-200; 28 | border-left-width: $spacer * .25; 29 | border-radius: $spacer * .25; 30 | h4 { 31 | margin-bottom: $spacer * .25; 32 | } 33 | p { 34 | &:last-child { 35 | margin-bottom: 0; 36 | } 37 | } 38 | + .callout { 39 | margin-top: -($spacer * .25); 40 | } 41 | &-primary { 42 | border-left-color: $primary; 43 | } 44 | &-info { 45 | border-left-color: $info; 46 | } 47 | &-warning { 48 | border-left-color: $warning; 49 | } 50 | &-danger { 51 | border-left-color: $danger; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /includes/default_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "qi_lang": "en", 3 | "dbms": "mysqli", 4 | "dbhost": "localhost", 5 | "dbport": "", 6 | "dbuser": "", 7 | "dbpasswd": "", 8 | "db_prefix": "qi_", 9 | "table_prefix": "phpbb_", 10 | "cache_dir": "cache\/", 11 | "boards_dir": "boards\/", 12 | "boards_url": "boards\/", 13 | "make_writable": 0, 14 | "grant_permissions": "", 15 | "qi_tz": "UTC", 16 | "admin_name": "admin", 17 | "admin_pass": "password", 18 | "admin_email": "qi_admin@phpbb-quickinstall.tld", 19 | "server_name": "localhost", 20 | "server_port": "80", 21 | "cookie_domain": "", 22 | "cookie_secure": 0, 23 | "board_email": "qi_board@phpbb-quickinstall.tld", 24 | "email_enable": 0, 25 | "smtp_delivery": 0, 26 | "smtp_host": "", 27 | "smtp_port": "25", 28 | "smtp_auth": "PLAIN", 29 | "smtp_user": "", 30 | "smtp_pass": "", 31 | "site_name": "Testing Board", 32 | "site_desc": "QuickInstall sandbox", 33 | "default_lang": "en", 34 | "other_config": "session_length;999999\n#load_tplcompile;1;1\n#This is a comment...", 35 | "chunk_post": 1000, 36 | "chunk_topic": 2000, 37 | "chunk_user": 5000, 38 | "redirect": 1, 39 | "populate": 0, 40 | "email_domain": "phpbb-quickinstall.tld", 41 | "num_users": 100, 42 | "num_new_group": 10, 43 | "create_admin": 1, 44 | "create_mod": 1, 45 | "num_cats": 2, 46 | "num_forums": 10, 47 | "num_topics_min": 5, 48 | "num_topics_max": 25, 49 | "num_replies_min": 0, 50 | "num_replies_max": 50, 51 | "no_dbpasswd": 0, 52 | "install_styles": 0, 53 | "default_style": "", 54 | "drop_db": "0", 55 | "delete_files": "0", 56 | "debug": "0", 57 | "alt_env": "" 58 | } 59 | -------------------------------------------------------------------------------- /includes/db/30x/mysql.php: -------------------------------------------------------------------------------- 1 | persistency = $persistency; 32 | $this->user = $sqluser; 33 | $this->server = $sqlserver . (($port) ? ':' . $port : ''); 34 | $this->dbname = $database; 35 | 36 | $this->sql_layer = 'mysql_40'; 37 | 38 | $this->db_connect_id = ($this->persistency) ? @mysql_pconnect($this->server, $this->user, $sqlpassword, $new_link) : @mysql_connect($this->server, $this->user, $sqlpassword, $new_link); 39 | 40 | if ($this->db_connect_id) 41 | { 42 | // Determine what version we are using and if it natively supports UNICODE 43 | $this->mysql_version = mysql_get_server_info($this->db_connect_id); 44 | 45 | if (version_compare($this->mysql_version, '4.1.3', '>=')) 46 | { 47 | @mysql_query("SET NAMES 'utf8'", $this->db_connect_id); 48 | } 49 | else if (version_compare($this->mysql_version, '4.0.0', '<')) 50 | { 51 | $this->sql_layer = 'mysql'; 52 | } 53 | 54 | return $this->db_connect_id; 55 | } 56 | 57 | return $this->sql_error(''); 58 | } 59 | 60 | /** 61 | * Select a database 62 | * 63 | * @param string $dbname 64 | */ 65 | function sql_select_db($dbname) 66 | { 67 | return @mysql_select_db($dbname, $this->db_connect_id); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /includes/db/mysqli.php: -------------------------------------------------------------------------------- 1 | = 80100) 35 | { 36 | @mysqli_report(MYSQLI_REPORT_OFF); 37 | } 38 | 39 | $this->persistency = $persistency; 40 | $this->user = $sqluser; 41 | $this->server = $sqlserver; 42 | $this->dbname = $database; 43 | $port = (!$port) ? null : $port; 44 | 45 | $this->sql_layer = 'mysql_41'; 46 | 47 | // Persistent connections not supported by the mysqli extension? 48 | $this->db_connect_id = @mysqli_connect($this->server, $this->user, $sqlpassword, null, $port); 49 | 50 | if ($this->db_connect_id) 51 | { 52 | @mysqli_query($this->db_connect_id, "SET NAMES 'utf8'"); 53 | return $this->db_connect_id; 54 | } 55 | 56 | return $this->sql_error(''); 57 | } 58 | 59 | /** 60 | * Select a database 61 | * 62 | * @param string $dbname 63 | * @return bool 64 | */ 65 | public function sql_select_db($dbname) 66 | { 67 | return @mysqli_select_db($this->db_connect_id, $dbname); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /includes/db/mysql.php: -------------------------------------------------------------------------------- 1 | persistency = $persistency; 32 | $this->user = $sqluser; 33 | $this->server = $sqlserver . (($port) ? ':' . $port : ''); 34 | $this->dbname = $database; 35 | 36 | $this->sql_layer = 'mysql4'; 37 | 38 | $this->db_connect_id = ($this->persistency) ? @mysql_pconnect($this->server, $this->user, $sqlpassword, $new_link) : @mysql_connect($this->server, $this->user, $sqlpassword, $new_link); 39 | 40 | if ($this->db_connect_id) 41 | { 42 | // Determine what version we are using and if it natively supports UNICODE 43 | $this->mysql_version = $this->sql_server_info(true); 44 | 45 | if (version_compare($this->mysql_version, '4.1.3', '>=')) 46 | { 47 | @mysql_query("SET NAMES 'utf8'", $this->db_connect_id); 48 | } 49 | else if (version_compare($this->mysql_version, '4.0.0', '<')) 50 | { 51 | $this->sql_layer = 'mysql'; 52 | } 53 | 54 | return $this->db_connect_id; 55 | } 56 | 57 | return $this->sql_error(''); 58 | } 59 | 60 | /** 61 | * Select a database 62 | * 63 | * @param string $dbname 64 | * @return bool 65 | */ 66 | public function sql_select_db($dbname) 67 | { 68 | return @mysql_select_db($dbname, $this->db_connect_id); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /style/error.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {{ ERROR_MSG_TITLE }} 18 | 19 | 20 |
21 | {% include 'navbar.twig' with {'S_ERROR': true} %} 22 |
23 |

{{ ERROR_MSG_TITLE }}

24 |
25 |
26 |
{{ ERROR_MSG_TEXT }}
27 |
28 |
29 |
30 | 34 |
35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /style/sidebar_boards.twig: -------------------------------------------------------------------------------- 1 |
2 |

{{ lang('BOARDS_LIST') }}

3 | {% for board in board_row %} 4 | {% if loop.first %} 5 |
6 |
7 | {% endif %} 8 |
9 |
10 |
11 | 12 | 13 |
14 | {% if board.VERSION %}{{ board.VERSION }}{% endif %} 15 |
16 |
17 | {% if loop.last %} 18 |
19 | 20 | 21 |
22 |
23 | 28 |
29 |
30 |
31 | {% endif %} 32 | {% else %} 33 |

{{ lang('NO_BOARDS') }}

34 | {% endfor %} 35 |
36 | -------------------------------------------------------------------------------- /modules/qi_docs.php: -------------------------------------------------------------------------------- 1 | text(file_get_contents($doc_file)); 24 | $doc_body = str_replace( 25 | ['', '
', '

'], 26 | ['

', '
', '

'], 27 | $doc_body 28 | ); 29 | $template->assign_var('DOC_BODY', $doc_body); 30 | } 31 | 32 | // GET CHANGELOG 33 | $changelog_file = $quickinstall_path . 'CHANGELOG.md'; 34 | if (file_exists($changelog_file)) 35 | { 36 | // let's get the changelog :) 37 | $data = file($changelog_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 38 | 39 | // We do not want the first line. 40 | unset($data[0]); 41 | 42 | foreach ($data as $row) 43 | { 44 | $row = ltrim($row); 45 | 46 | if (strpos($row, '#') === 0) 47 | { 48 | $key = substr($row, 3); 49 | 50 | $template->assign_block_vars('history', array( 51 | 'CHANGES_SINCE' => $key, 52 | 53 | 'U_CHANGES' => strtolower(str_replace(array(' ', '.'), array('-', ''), $key)), 54 | )); 55 | } 56 | else if (strpos($row, '-') === 0) 57 | { 58 | $change = substr($row, 2); 59 | $change = str_replace( 60 | ['[Fix]', '[Change]', '[Feature]'], 61 | ['Fix', 'Change', 'Feature'], 62 | $change); 63 | 64 | $template->assign_block_vars('history.changelog', array( 65 | 'CHANGE' => Parsedown::instance()->line($change), 66 | )); 67 | } 68 | } 69 | } 70 | 71 | $template->assign_var('S_DOCS', true); 72 | 73 | // Output page 74 | qi::page_header('DOCS_LONG'); 75 | 76 | qi::page_display('docs_body'); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /includes/functions_forum_create.php: -------------------------------------------------------------------------------- 1 | ..., 'value' => ..., 'column_type' =>))' 28 | * @param mixed $error The error array 29 | */ 30 | function validate_range($value_ary, &$error) 31 | { 32 | $column_types = array( 33 | 'BOOL' => array('php_type' => 'int', 'min' => 0, 'max' => 1), 34 | 'USINT' => array('php_type' => 'int', 'min' => 0, 'max' => 65535), 35 | 'UINT' => array('php_type' => 'int', 'min' => 0, 'max' => (int) 0x7fffffff), 36 | 'INT' => array('php_type' => 'int', 'min' => (int) 0x80000000, 'max' => (int) 0x7fffffff), 37 | 'TINT' => array('php_type' => 'int', 'min' => -128, 'max' => 127), 38 | 39 | 'VCHAR' => array('php_type' => 'string', 'min' => 0, 'max' => 255), 40 | ); 41 | foreach ($value_ary as $value) 42 | { 43 | $column = explode(':', $value['column_type']); 44 | if (!isset($column_types[$column[0]])) 45 | { 46 | continue; 47 | } 48 | 49 | $type = $column_types[$column[0]]; 50 | 51 | switch ($type['php_type']) 52 | { 53 | case 'string' : 54 | $max = (isset($column[1])) ? min($column[1],$type['max']) : $type['max']; 55 | if (strlen($value['value']) > $max) 56 | { 57 | $error[] = qi::lang('SETTING_TOO_LONG', qi::lang($value['lang']), $max); 58 | } 59 | break; 60 | 61 | case 'int': 62 | $min = (isset($column[1])) ? max($column[1],$type['min']) : $type['min']; 63 | $max = (isset($column[2])) ? min($column[2],$type['max']) : $type['max']; 64 | if ($value['value'] < $min) 65 | { 66 | $error[] = qi::lang('SETTING_TOO_LOW', qi::lang($value['lang']), $min); 67 | } 68 | else if ($value['value'] > $max) 69 | { 70 | $error[] = qi::lang('SETTING_TOO_BIG', qi::lang($value['lang']), $max); 71 | } 72 | break; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /style/navbar.twig: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /includes/db/sqlite3.php: -------------------------------------------------------------------------------- 1 | server 26 | */ 27 | protected $port; 28 | 29 | /** 30 | * Connect to server 31 | */ 32 | public function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false , $new_link = false) 33 | { 34 | $this->persistency = $persistency; 35 | $this->user = $sqluser; 36 | $this->server = $sqlserver . ($port ? ':' . $port : ''); 37 | $this->dbname = $database; 38 | 39 | $this->port = $port; 40 | 41 | $this->sql_layer = 'sqlite3'; 42 | 43 | // connect to db 44 | $this->sql_select_db(); 45 | 46 | return $this->db_connect_id ? true : array('message' => $this->connect_error); 47 | } 48 | 49 | /** 50 | * Select a database 51 | * 52 | * @param string $dbname 53 | * @return bool 54 | */ 55 | public function sql_select_db($dbname = '') 56 | { 57 | if (!file_exists($dbname)) 58 | { 59 | return false; 60 | } 61 | 62 | $this->server = $dbname ? ($dbname . ($this->port ? ':' . $this->port : '')) : $this->server; 63 | 64 | try 65 | { 66 | $this->dbo = new \SQLite3($this->server,SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE); 67 | $this->dbo->busyTimeout(60000); 68 | $this->db_connect_id = true; 69 | } 70 | catch (\Exception $e) 71 | { 72 | $this->connect_error = $e->getMessage(); 73 | $this->db_connect_id = false; 74 | } 75 | 76 | return $this->db_connect_id; 77 | } 78 | 79 | /** 80 | * Create a database 81 | * 82 | * @param string $dbname 83 | */ 84 | public function sql_create_db($dbname) 85 | { 86 | if (!file_exists($dbname)) 87 | { 88 | // if file doesn't exist, attempt to create it. 89 | if (!is_writable(dirname($dbname))) 90 | { 91 | trigger_error('SQLite: unable to write to dir ' . dirname($dbname), E_USER_ERROR); 92 | } 93 | 94 | $fp = @fopen($dbname, 'ab'); 95 | @fclose($fp); 96 | @chmod($dbname, 0777); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "GPL-2.0-only", 3 | "dependencies": { 4 | "autoprefixer": "^10.4.2", 5 | "bootstrap": "5.1.3", 6 | "bootstrap-dark-5": "^1.0.1", 7 | "bootstrap-icons": "^1.8.0", 8 | "clean-css-cli": "^5.5.2", 9 | "copyfiles": "^2.4.0", 10 | "eslint": "^8.57.1", 11 | "npm-run-all": "^4.1.5", 12 | "postcss-cli": "^10.1.0", 13 | "sass": "^1.71.1" 14 | }, 15 | "scripts": { 16 | "css": "npm-run-all css-compile css-prefix css-minify", 17 | "css-compile": "sass scss/qi_bootstrap.scss scss/qi_bootstrap.css", 18 | "css-prefix": "postcss --use autoprefixer --replace scss/qi_bootstrap.css", 19 | "css-minify": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output scss/qi_bootstrap.min.css scss/qi_bootstrap.css", 20 | "copy": "npm-run-all --parallel copy-*", 21 | "copy-bs-css": "copyfiles --flat scss/qi_bootstrap.min.css scss/qi_bootstrap.min.css.map style/assets/css", 22 | "copy-bs-js": "copyfiles --flat node_modules/bootstrap/dist/js/bootstrap.min.js node_modules/bootstrap/dist/js/bootstrap.min.js.map style/assets/js", 23 | "copy-bs-icons": "copyfiles --flat node_modules/bootstrap-icons/bootstrap-icons.svg style/assets/img", 24 | "copy-bs-dark": "copyfiles --flat node_modules/bootstrap-dark-5/dist/css/bootstrap-nightfall.min.css style/assets/css", 25 | "all": "npm-run-all css copy", 26 | "test": "eslint style/assets/js/scripts.js" 27 | }, 28 | "eslintConfig": { 29 | "ignorePatterns": [ 30 | "style/assets/js/bootstrap.*.js" 31 | ], 32 | "rules": { 33 | "quotes": [ 34 | "error", 35 | "single" 36 | ], 37 | "comma-dangle": [ 38 | "error", 39 | "always-multiline" 40 | ], 41 | "max-params": [ 42 | "error", 43 | 6 44 | ], 45 | "block-spacing": "error", 46 | "array-bracket-spacing": [ 47 | "error", 48 | "always" 49 | ], 50 | "multiline-comment-style": "error", 51 | "computed-property-spacing": "off", 52 | "space-in-parens": "off", 53 | "capitalized-comments": "off", 54 | "object-curly-spacing": [ 55 | "error", 56 | "always" 57 | ], 58 | "no-lonely-if": "off", 59 | "unicorn/prefer-module": "off", 60 | "space-before-function-paren": [ 61 | "error", 62 | "never" 63 | ] 64 | }, 65 | "env": { 66 | "es2021": true, 67 | "browser": true, 68 | "node": true 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to QuickInstall 2 | 3 | :+1::tada: Thanks for taking the time to contribute! :tada::+1: 4 | 5 | ## Contents: 6 | 1. [Fork and Clone](#fork_and_knife-fork-and-clone) 7 | 2. [Gear up for development](#gear-gear-up-for-development) 8 | 3. [Make something great](#computer-make-something-great) 9 | 4. [Submit a Pull Request](#tophat-submit-a-pull-request) 10 | 5. [Collaborate](#thumbsup-collaborate) 11 | 12 | ## :fork_and_knife: Fork and Clone 13 | 14 | First steps include creating your own repository of QuickInstall, and getting a copy of it onto your computer: 15 | 16 | 1. On GitHub, Fork your own copy of `phpbb/quickinstall` to your account. 17 | 18 | 2. Create a local clone of your fork: 19 | ``` 20 | $ git clone git://github.com//quickinstall.git 21 | ``` 22 | 23 | ## :gear: Gear up for development 24 | 25 | Assuming you have a local web development server up and running and know how to use a command line terminal application: 26 | 27 | 1. From QI's root directory, run the following command to install QI's dependencies: 28 | ``` 29 | $ php composer.phar install 30 | ``` 31 | 32 | 2. Open QuickInstall in a browser on your local web server (e.g., `https://localhost/quickinstall`). 33 | 34 | > Optional: QuickInstall uses the Bootstrap framework which is compiled via NPM. To update or customise QuickInstall's Bootstrap files you must: 35 | > - Have [Node JS](https://nodejs.org/) installed. 36 | > - Run `$ npm install` to install its node dependencies. 37 | > - Edit the `scss/qi_bootstrap.scss` file to customise Bootstrap variables. 38 | > - Run `$ npm run all` to compile and deploy new Bootstrap CSS/JS files to QuickInstall. 39 | 40 | 41 | ## :computer: Make something great 42 | 43 | 1. Create a new branch in your repository before doing any work. It should be based off the `develop` branch: 44 | ``` 45 | $ git checkout -b myNewbranch origin/develop 46 | ``` 47 | 48 | 2. Do work on your branch, commit your changes and push it to your repository: 49 | ``` 50 | $ git commit -a -m "My new feature or bug fixes" 51 | 52 | $ git push origin myNewbranch 53 | ``` 54 | 55 | 56 | ## :tophat: Submit a Pull Request 57 | 58 | 1. Go to your repository on GitHub.com. 59 | 60 | 2. Click the Pull Request button. 61 | 62 | 63 | ## :thumbsup: Collaborate 64 | 65 | Be prepared for: 66 | - Constructive criticism of your code changes. 67 | - phpBB team members, or the community at large may request changes to your code (repeat [step 2 from here](#computer-make-something-great)). 68 | - That feeling when your Pull Request is accepted and merged. :sunglasses: 69 | 70 | -------------------------------------------------------------------------------- /includes/db/30x/sqlite.php: -------------------------------------------------------------------------------- 1 | server 33 | */ 34 | var $port; 35 | 36 | /** 37 | * Connect to server 38 | */ 39 | function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false , $new_link = false) 40 | { 41 | $this->persistency = $persistency; 42 | $this->user = $sqluser; 43 | $this->server = $sqlserver . (($port) ? ':' . $port : ''); 44 | $this->dbname = $database; 45 | 46 | $this->port = $port; 47 | 48 | // connect to db 49 | $this->sql_select_db($this->server); 50 | 51 | return ($this->db_connect_id) ? true : array('message' => $error); 52 | } 53 | 54 | /** 55 | * Select a database 56 | * 57 | * @param string $dbname 58 | */ 59 | function sql_select_db($dbname) 60 | { 61 | if (!file_exists($dbname)) 62 | { 63 | return false; 64 | // if file doesn't exist, attempt to create it. 65 | if (!is_writable(dirname($dbname))) 66 | { 67 | trigger_error('SQLite: unable to write to dir ' . dirname($dbname), E_USER_ERROR); 68 | } 69 | 70 | $fp = @fopen($dbname, 'a'); 71 | @fclose($fp); 72 | @chmod($dbname, 0777); 73 | } 74 | 75 | $this->server = $dbname . (($this->port) ? ':' . $this->port : ''); 76 | 77 | $this->db_connect_id = ($this->persistency) ? @sqlite_popen($this->server, 0666, $this->error) : @sqlite_open($this->server, 0666, $this->error); 78 | 79 | if ($this->db_connect_id) 80 | { 81 | @sqlite_query('PRAGMA short_column_names = 1', $this->db_connect_id); 82 | } 83 | 84 | return $this->db_connect_id; 85 | } 86 | 87 | /** 88 | * Select a database 89 | * 90 | * @param string $dbname 91 | */ 92 | function sql_create_db($dbname) 93 | { 94 | if (!file_exists($dbname)) 95 | { 96 | // if file doesn't exist, attempt to create it. 97 | if (!is_writable(dirname($dbname))) 98 | { 99 | trigger_error('SQLite: unable to write to dir ' . dirname($dbname), E_USER_ERROR); 100 | } 101 | 102 | $fp = @fopen($dbname, 'a'); 103 | @fclose($fp); 104 | @chmod($dbname, 0777); 105 | } 106 | 107 | return $this->db_connect_id; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /includes/db/sqlite.php: -------------------------------------------------------------------------------- 1 | server 33 | */ 34 | var $port; 35 | 36 | /** 37 | * Connect to server 38 | */ 39 | function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false , $new_link = false) 40 | { 41 | $this->persistency = $persistency; 42 | $this->user = $sqluser; 43 | $this->server = $sqlserver . (($port) ? ':' . $port : ''); 44 | $this->dbname = $database; 45 | 46 | $this->port = $port; 47 | 48 | // connect to db 49 | $this->sql_select_db($this->server); 50 | 51 | return ($this->db_connect_id) ? true : array('message' => $error); 52 | } 53 | 54 | /** 55 | * Select a database 56 | * 57 | * @param string $dbname 58 | */ 59 | function sql_select_db($dbname) 60 | { 61 | if (!file_exists($dbname)) 62 | { 63 | return false; 64 | // if file doesn't exist, attempt to create it. 65 | if (!is_writable(dirname($dbname))) 66 | { 67 | trigger_error('SQLite: unable to write to dir ' . dirname($dbname), E_USER_ERROR); 68 | } 69 | 70 | $fp = @fopen($dbname, 'a'); 71 | @fclose($fp); 72 | @chmod($dbname, 0777); 73 | } 74 | 75 | $this->server = $dbname . (($this->port) ? ':' . $this->port : ''); 76 | 77 | $this->db_connect_id = ($this->persistency) ? @sqlite_popen($this->server, 0666, $this->error) : @sqlite_open($this->server, 0666, $this->error); 78 | 79 | if ($this->db_connect_id) 80 | { 81 | @sqlite_query('PRAGMA short_column_names = 1', $this->db_connect_id); 82 | } 83 | 84 | return $this->db_connect_id; 85 | } 86 | 87 | /** 88 | * Select a database 89 | * 90 | * @param string $dbname 91 | */ 92 | function sql_create_db($dbname) 93 | { 94 | if (!file_exists($dbname)) 95 | { 96 | // if file doesn't exist, attempt to create it. 97 | if (!is_writable(dirname($dbname))) 98 | { 99 | trigger_error('SQLite: unable to write to dir ' . dirname($dbname), E_USER_ERROR); 100 | } 101 | 102 | $fp = @fopen($dbname, 'a'); 103 | @fclose($fp); 104 | @chmod($dbname, 0777); 105 | } 106 | 107 | return $this->db_connect_id; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /style/overall_header.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {{ PAGE_TITLE }} • {{ lang('PHPBB_QI_TITLE') }} 18 | 19 | 20 | 21 | 22 | {% if U_VERSION_CHECK_URL %} 23 |
24 | 32 |
33 | {% endif %} 34 | 35 |
36 |
37 | 48 |
49 | {% include 'navbar.twig' %} 50 |
51 | 56 | -------------------------------------------------------------------------------- /style/sidebar_settings.twig: -------------------------------------------------------------------------------- 1 |

{{ lang('SETTINGS_SECTIONS') }}

2 | 15 | -------------------------------------------------------------------------------- /modules/qi_main.php: -------------------------------------------------------------------------------- 1 | assign_vars(array( 23 | 'U_CREATE' => qi::url('create'), 24 | 'U_CHOOSE_PROFILE' => qi::url('main', array('mode' => 'change_profile')), 25 | 26 | 'TABLE_PREFIX' => $settings->get_config('table_prefix', ''), 27 | 'DB_PREFIX' => $settings->get_config('db_prefix', ''), 28 | 'SITE_NAME' => $settings->get_config('site_name', ''), 29 | 'SITE_DESC' => $settings->get_config('site_desc', ''), 30 | 'PROFILES' => $settings->get_profiles(), 31 | 'DBNAME' => $settings->get_config('dbname', ''), 32 | 'INSTALL_STYLES' => $settings->get_config('install_styles', 0), 33 | 'DEFAULT_STYLE' => $settings->get_config('default_style', ''), 34 | 35 | 'S_DELETE_FILES'=> $settings->get_config('delete_files', 0), 36 | 'S_DEBUG' => $settings->get_config('debug', 0), 37 | 'S_DROP_DB' => $settings->get_config('drop_db', 0), 38 | 'S_MAKE_WRITABLE' => $settings->get_config('make_writable', 0), 39 | 'S_POPULATE' => $settings->get_config('populate', 0), 40 | 'S_REDIRECT' => $settings->get_config('redirect', 0), 41 | 42 | 'ADMIN_NAME' => $settings->get_config('admin_name', ''), 43 | 'ADMIN_PASS' => $settings->get_config('admin_pass', ''), 44 | 'S_DBPASSWD' => $settings->get_config('dbpasswd', false), 45 | 'S_NODBPASSWD' => $settings->get_config('no_dbpasswd', false), 46 | 'S_DBUSER' => $settings->get_config('dbuser', false), 47 | 'S_BOARDS' => true, 48 | 49 | 'ALT_ENV' => get_alternative_env($settings->get_config('alt_env', '')), 50 | 51 | 'PHPBB_VERSION' => qi_get_phpbb_version(), 52 | 53 | // Chunk settings 54 | 'CHUNK_POST' => $settings->get_config('chunk_post', 0), 55 | 'CHUNK_TOPIC' => $settings->get_config('chunk_topic', 0), 56 | 'CHUNK_USER' => $settings->get_config('chunk_user', 0), 57 | 58 | // Populate settings. 59 | 'NUM_USERS' => $settings->get_config('num_users', 0), 60 | 'NUM_NEW_GROUP' => $settings->get_config('num_new_group', 0), 61 | 'CREATE_MOD' => $settings->get_config('create_mod', 0), 62 | 'CREATE_ADMIN' => $settings->get_config('create_admin', 0), 63 | 'NUM_CATS' => $settings->get_config('num_cats', 0), 64 | 'NUM_FORUMS' => $settings->get_config('num_forums', 0), 65 | 'NUM_TOPICS_MIN' => $settings->get_config('num_topics_min', 0), 66 | 'NUM_TOPICS_MAX' => $settings->get_config('num_topics_max', 0), 67 | 'NUM_REPLIES_MIN' => $settings->get_config('num_replies_min', 0), 68 | 'NUM_REPLIES_MAX' => $settings->get_config('num_replies_max', 0), 69 | 'EMAIL_DOMAIN' => $settings->get_config('email_domain', ''), 70 | 'GRANT_PERMISSIONS' => $settings->get_config('grant_permissions', ''), 71 | 'OTHER_CONFIG' => $settings->get_config('other_config', ''), 72 | )); 73 | 74 | // Output page 75 | qi::page_header('BOARDS'); 76 | 77 | qi::page_display('main_body'); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /modules/qi_phpinfo.php: -------------------------------------------------------------------------------- 1 | ]*>(.*)#si', $phpinfo, $output); 27 | 28 | if (empty($phpinfo) || empty($output)) 29 | { 30 | trigger_error(qi::lang('NO_PHPINFO_AVAILABLE'), E_USER_WARNING); 31 | } 32 | 33 | $output = $output[1][0]; 34 | 35 | // expose_php can make the image not exist 36 | if (preg_match('#]*>]*>#', $output)) 37 | { 38 | $output = preg_replace('#

#s', '', $output); 39 | } 40 | else 41 | { 42 | $output = preg_replace('##s', '', $output); 43 | } 44 | $output = preg_replace('#]*>#i', '
(.*?]*>]*>)(.*?)
\2\1
(.*?)
\1
', $output); 45 | $output = preg_replace('#', ''), array('', '', '', '', ''), $output); 47 | // Add searchable class to all but the 1st table 48 | $output = preg_replace_callback('#
#', function($matches) { 49 | static $index = 0; 50 | if ($index++ === 0) 51 | { 52 | return $matches[0]; 53 | } 54 | return '
'; 55 | }, $output); 56 | // Process all the anchors for the menu 57 | $anchor = '#(.*)#'; 58 | preg_match_all($anchor, $output, $matches); 59 | foreach ($matches[1] as $key => $match) 60 | { 61 | $template->assign_block_vars('phpinfo', array( 62 | 'U_ANCHOR' => $matches[1][$key], 63 | 'TITLE' => $matches[2][$key], 64 | )); 65 | } 66 | $output = preg_replace($anchor, '$2', $output); 67 | 68 | // Fix invalid anchor names (eg "module_Zend Optimizer") 69 | $output = preg_replace_callback('##', array($this, 'remove_spaces'), $output); 70 | 71 | if (empty($output)) 72 | { 73 | trigger_error(qi::lang('NO_PHPINFO_AVAILABLE'), E_USER_WARNING); 74 | } 75 | 76 | $orig_output = $output; 77 | 78 | preg_match_all('#
(.*)
#siU', $output, $output); 79 | $output = (!empty($output[1][0])) ? $output[1][0] : $orig_output; 80 | 81 | $template->assign_vars(array( 82 | 'S_PHPINFO' => true, 83 | 'PHPINFO' => $output, 84 | 'U_DELETE_COOKIES' => qi::url('phpinfo') . '&delete-cookies=' . qi::PHPBB_COOKIE_PREFIX, 85 | )); 86 | 87 | // Output page 88 | qi::page_header('PHPINFO'); 89 | 90 | qi::page_display('phpinfo_body'); 91 | } 92 | 93 | public function remove_spaces($matches) 94 | { 95 | return '
'; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /includes/class_31_styles.php: -------------------------------------------------------------------------------- 1 | 6 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 7 | * 8 | */ 9 | 10 | /** 11 | * @ignore 12 | */ 13 | if (!defined('IN_QUICKINSTALL')) 14 | { 15 | exit; 16 | } 17 | 18 | class class_31_styles extends acp_styles 19 | { 20 | private $qi_styles = array(); 21 | 22 | private $qi_default_style; 23 | 24 | public function __construct() 25 | { 26 | global $db, $user, $phpbb_root_path, $phpEx, $template, $request, $cache, $auth, $config, $settings; 27 | 28 | $this->mode = 'install'; 29 | $this->db = $db; 30 | $this->user = $user; 31 | $this->template = $template; 32 | $this->request = $request; 33 | $this->cache = $cache; 34 | $this->auth = $auth; 35 | $this->config = $config; 36 | $this->phpbb_root_path = $phpbb_root_path; 37 | $this->php_ext = $phpEx; 38 | $this->styles_path = $this->phpbb_root_path . $this->styles_path_absolute . '/'; 39 | 40 | $this->qi_default_style = $settings->get_config('default_style', ''); 41 | 42 | // Get a array with installed styles. 43 | // Should only contain prosilver 44 | $installed = $this->get_styles(); 45 | 46 | // Get a array with all not installed styles. 47 | $available = $this->find_available(true); 48 | 49 | // And merge them into one array. 50 | $style_ary = array_merge($installed, $available); 51 | 52 | // Set styles as active and put their name as key. 53 | foreach ($style_ary as $style) 54 | { 55 | $style['style_active'] = 1; 56 | $this->qi_styles[$style['style_name']] = $style; 57 | } 58 | 59 | unset($style_ary); 60 | 61 | // We have the needed style data. 62 | foreach ($this->qi_styles as $key => $style) 63 | { 64 | if (empty($this->qi_styles[$key]['style_id'])) 65 | { 66 | $this->qi_install_style($style); 67 | } 68 | } 69 | } 70 | 71 | private function qi_install_style($style) 72 | { 73 | if (!empty($style['_inherit_name']) && empty($this->qi_styles[$style['_inherit_name']])) 74 | { 75 | // We don't have the parent skip this. 76 | return; 77 | } 78 | 79 | if (!empty($style['_inherit_name']) && empty($this->qi_styles[$style['_inherit_name']]['style_id'])) 80 | { 81 | // Need to install the parent first. 82 | $this->qi_install_style($this->qi_styles[$style['_inherit_name']]); 83 | } 84 | 85 | if (!empty($style['_inherit_name']) && !empty($this->qi_styles[$style['_inherit_name']]['style_id'])) 86 | { 87 | // Set parent id. 88 | $style['style_parent_id'] = $this->qi_styles[$style['style_name']]['style_parent_id'] = $this->qi_styles[$style['_inherit_name']]['style_id']; 89 | $style['style_parent_tree'] = $this->qi_styles[$style['style_name']]['style_parent_tree'] = $this->qi_styles[$style['_inherit_name']]['style_path']; 90 | } 91 | 92 | $id = $this->install_style($style); 93 | $this->qi_styles[$style['style_name']]['style_id'] = $id; 94 | 95 | if ($this->qi_default_style == $style['style_name']) 96 | { 97 | $this->qi_set_default($id); 98 | } 99 | } 100 | 101 | private function qi_set_default($id) 102 | { 103 | qi_set_config('default_style', $id); 104 | 105 | // Set it for guests and the admin too. 106 | $sql = 'UPDATE ' . USERS_TABLE . ' 107 | SET user_style = ' . (int) $id . ' 108 | WHERE user_id = 1 or user_id = 2'; 109 | $this->db->sql_query($sql); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /modules/qi_manage.php: -------------------------------------------------------------------------------- 1 | ''), true); 24 | $boards = count($select); 25 | $error = array(); 26 | 27 | foreach ($select as $item) 28 | { 29 | $current_item = $settings->get_boards_dir() . $item; 30 | 31 | // First get config-file data for the board 32 | $cfg_file = $current_item . '/config.' . $phpEx; 33 | $dbhost = $dbport = $dbname = $dbuser = $dbpasswd = $dbms = ''; 34 | if (file_exists($cfg_file)) 35 | { 36 | include $cfg_file; 37 | } 38 | 39 | // Attempt to delete the board from filesystem 40 | if (!file_exists($current_item) || !is_dir($current_item)) 41 | { 42 | continue; 43 | } 44 | 45 | qi_file::delete_dir($current_item); 46 | 47 | if (!empty(qi_file::$error)) 48 | { 49 | if ($boards > 1) 50 | { 51 | $error[] = $current_item; 52 | qi_file::$error = array(); 53 | } 54 | else 55 | { 56 | $error = qi_file::$error; 57 | } 58 | } 59 | 60 | // Attempt to delete the database 61 | if (!empty($dbname) && !empty($dbhost) && !empty($dbms) && empty($error)) 62 | { 63 | $dbms = (strpos($dbms, '\\') !== false) ? substr(strrchr($dbms, '\\'), 1) : $dbms; 64 | 65 | if (in_array($dbms, array('sqlite', 'sqlite3'))) 66 | { 67 | $db_file = $dbhost . $dbname; 68 | 69 | if (file_exists($db_file)) 70 | { 71 | // Assuming the DB file is created by PHP, then PHP should also have permissions to delete it. 72 | @unlink($db_file); 73 | } 74 | else if (file_exists($dbhost)) 75 | { 76 | // Assuming the DB file is created by PHP, then PHP should also have permissions to delete it. 77 | @unlink($dbhost); 78 | } 79 | } 80 | else if (!empty($dbuser) && !empty($dbpasswd)) 81 | { 82 | // The order here is important, don't change it. 83 | $db_vars = array( 84 | $dbms, 85 | $dbhost, 86 | $dbuser, 87 | $dbpasswd, 88 | $dbport, 89 | ); 90 | 91 | $db = db_connect($db_vars); 92 | $db->sql_query('DROP DATABASE IF EXISTS ' . $dbname); 93 | db_close($db); // Might give a error since the DB it deleted, needs to be more tested. 94 | } 95 | } 96 | } 97 | 98 | if (empty($error)) 99 | { 100 | // Just return to main page after successful deletion. 101 | qi::redirect('index.' . $phpEx); 102 | } 103 | else 104 | { 105 | $header = $boards > 1 ? 'ERROR_DEL_BOARDS' : 'ERROR_DEL_FILES'; 106 | 107 | $message = '
' . qi::lang($header) . '
'; 108 | foreach ($error as $row) 109 | { 110 | $message .= '

' . htmlspecialchars($row) . '

'; 111 | } 112 | 113 | if (strlen($message) > 1024) 114 | { 115 | // We need to define $msg_long_text here to circumvent text stripping. 116 | global $msg_long_text; 117 | $msg_long_text = $message; 118 | 119 | trigger_error(false, E_USER_NOTICE); 120 | } 121 | 122 | trigger_error($message, E_USER_NOTICE); 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /style/assets/css/qi_style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --darkside-color: hsl(0, 0%, 70%); 3 | --darkside-color-dim: hsl(0, 0%, 50%); 4 | --darkside-bg-color: hsl(219, 20%, 20%); 5 | --darkside-bg-hilight: hsla(0, 0%, 100%, 0.12); 6 | --night-bg-color: hsl(216, 28%, 7%); 7 | --night-color: hsl(210, 17%, 82%); 8 | --night-color-muted: hsl(212, 9%, 58%); 9 | /* SVG color filters https://codepen.io/sosuke/pen/Pjoqqp */ 10 | --svg-filter-e1e1e1: brightness(0) saturate(100%) invert(74%) sepia(97%) saturate(1%) hue-rotate(315deg) brightness(98%) contrast(103%); 11 | --svg-filter-009bdf: brightness(0) saturate(100%) invert(46%) sepia(52%) saturate(3716%) hue-rotate(170deg) brightness(96%) contrast(101%); 12 | --svg-filter-fafafa: brightness(0) saturate(100%) invert(100%) sepia(5%) saturate(23%) hue-rotate(84deg) brightness(104%) contrast(96%); 13 | } 14 | 15 | .phpbb-logo-nb { 16 | background-image: url("../img/logo_small_white.svg"); 17 | background-repeat: no-repeat; 18 | padding: 0.125rem 3rem; 19 | margin-left: 0.5rem; 20 | filter: var(--svg-filter-009bdf); 21 | } 22 | 23 | .navbar-light .navbar-toggler-icon-qi-boards { 24 | background-image: url("data:image/svg+xml,"); 25 | } 26 | 27 | /* workaround to reposition anchor links under navbar */ 28 | .anchor { 29 | margin-top: -4rem; 30 | padding-top: 4rem; 31 | } 32 | 33 | /* sidebar styling */ 34 | .sidebar { 35 | top: 0; 36 | bottom: 0; 37 | left: 0; 38 | z-index: 1000; 39 | overflow-y: scroll; /* Scrollable contents if viewport is shorter than content. */ 40 | min-width: 185px; 41 | background-color: var(--darkside-bg-color); 42 | color: var(--darkside-color); 43 | } 44 | 45 | .sidebar a { 46 | color: var(--darkside-color); 47 | } 48 | 49 | .sidebar .nav-link { 50 | padding: .2rem 1rem .2rem .5rem; 51 | } 52 | 53 | .sidebar .nav-link.active { 54 | background-color: var(--darkside-bg-hilight); 55 | border-radius: .25rem; 56 | } 57 | 58 | .sidebar .nav-link svg.bi { 59 | line-height: 1.5rem; 60 | color: var(--darkside-color-dim); 61 | } 62 | 63 | .sidebar .nav-link:hover, 64 | .sidebar .nav-link:hover svg.bi, 65 | .sidebar .nav-link.active svg.bi { 66 | color: inherit; 67 | } 68 | 69 | .sidebar .list-group-item, 70 | .sidebar .border-bottom, 71 | .sidebar .border-top { 72 | border-color: var(--darkside-bg-hilight) !important; /* !important to override bootstrap's !important */ 73 | } 74 | 75 | .sidebar .badge { 76 | background-color: var(--darkside-bg-hilight); 77 | color: var(--darkside-color); 78 | } 79 | 80 | .badge { 81 | line-height: 1em; 82 | } 83 | 84 | svg.bi { 85 | vertical-align: -.125em; 86 | } 87 | 88 | /* Dark mode adjustments */ 89 | @media (prefers-color-scheme: dark) { 90 | body, 91 | body.bg-secondary, 92 | .navbar, 93 | .navbar.bg-secondary { 94 | background-color: var(--night-bg-color) !important; /* !important to override bootstrap's !important */ 95 | color: var(--night-color) !important; /* !important to override bootstrap's !important */ 96 | } 97 | 98 | .text-muted { 99 | color: var(--night-color-muted) !important; /* !important to override bootstrap's !important */ 100 | } 101 | 102 | .callout-primary { border-left-color: #375a7f; } 103 | .callout-info { border-left-color: #17a2b8; } 104 | .callout-warning { border-left-color: #f39c12; } 105 | .callout-danger { border-left-color: #e74c3c; } 106 | 107 | .phpbb-logo-rm { 108 | filter: var(--svg-filter-e1e1e1); 109 | } 110 | 111 | .navbar-brand { 112 | filter: var(--svg-filter-fafafa); 113 | } 114 | 115 | .navbar-light .navbar-toggler-icon-qi-boards { 116 | background-image: url("data:image/svg+xml,"); 117 | } 118 | } 119 | 120 | @media (max-width: 767.98px) { 121 | .sidebar { 122 | top: 3.7rem; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /includes/qi_file.php: -------------------------------------------------------------------------------- 1 | 0) && copy($src_file, $dst_file)) 81 | { 82 | touch($dst_file, filemtime($src_file)); 83 | } 84 | } 85 | else if (is_dir($src_file)) 86 | { 87 | self::copy_dir($src_file, $dst_file); 88 | } 89 | } 90 | } 91 | 92 | public static function delete_dir($dir, $empty = false) 93 | { 94 | self::append_slash($dir); 95 | 96 | if (!file_exists($dir) || !is_dir($dir) || !is_readable($dir)) 97 | { 98 | return; 99 | } 100 | 101 | foreach (scandir($dir) as $file) 102 | { 103 | if (in_array($file, array('.', '..'), true)) 104 | { 105 | continue; 106 | } 107 | 108 | if (is_dir($dir . $file)) 109 | { 110 | self::delete_dir($dir . $file); 111 | } 112 | else 113 | { 114 | self::delete_file($dir . $file); 115 | } 116 | } 117 | 118 | if (!$empty) 119 | { 120 | @rmdir($dir); 121 | } 122 | } 123 | 124 | public static function delete_files($dir, $files_ary, $recursive = true) 125 | { 126 | self::append_slash($dir); 127 | 128 | foreach (scandir($dir) as $file) 129 | { 130 | if (in_array($file, array('.', '..'), true)) 131 | { 132 | continue; 133 | } 134 | 135 | if ($recursive && is_dir($dir . $file)) 136 | { 137 | self::delete_files($dir . $file, $files_ary, true); 138 | } 139 | 140 | if (in_array($file, $files_ary, true)) 141 | { 142 | if (is_dir($dir . $file)) 143 | { 144 | self::delete_dir($dir . $file); 145 | } 146 | else 147 | { 148 | self::delete_file($dir . $file); 149 | } 150 | } 151 | } 152 | } 153 | 154 | public static function append_slash(&$dir) 155 | { 156 | if ($dir[strlen($dir) - 1] !== '/') 157 | { 158 | $dir .= '/'; 159 | } 160 | } 161 | 162 | /** 163 | * Recursive make all files and directories world writable. 164 | * 165 | * @param string $dir 166 | * @param bool $root 167 | */ 168 | public static function make_writable($dir, $root = true) 169 | { 170 | self::grant_permissions($dir, 0666, $root); 171 | } 172 | 173 | public static function grant_permissions($dir, $add_perms, $root = true) 174 | { 175 | $old_perms = fileperms($dir); 176 | $new_perms = $old_perms | $add_perms; 177 | if ($new_perms != $old_perms) 178 | { 179 | chmod($dir, $new_perms); 180 | } 181 | 182 | if (is_dir($dir)) 183 | { 184 | $file_arr = scandir($dir); 185 | $dir .= '/'; 186 | 187 | foreach ($file_arr as $file) 188 | { 189 | if ($file === '.' || $file === '..') 190 | { 191 | continue; 192 | } 193 | 194 | //if ($root && $file == 'config.' . $phpEx) 195 | //{ 196 | // chmod($dir . $file, 0666); 197 | // continue; 198 | //} 199 | 200 | $file = $dir . $file; 201 | self::grant_permissions($file, $add_perms, false); 202 | } 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /includes/class_30_styles.php: -------------------------------------------------------------------------------- 1 | 6 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 7 | * 8 | */ 9 | 10 | /** 11 | * @ignore 12 | */ 13 | if (!defined('IN_QUICKINSTALL')) 14 | { 15 | exit; 16 | } 17 | 18 | class class_30_styles 19 | { 20 | private $qi_default_style; 21 | 22 | private $qi_styles = array(); 23 | 24 | private $styles_path = 'styles/'; 25 | 26 | private $acp_styles; 27 | 28 | public function __construct() 29 | { 30 | global $settings, $phpbb_root_path; 31 | 32 | $this->styles_path = $phpbb_root_path . $this->styles_path; 33 | 34 | $this->acp_styles = new acp_styles(); 35 | $this->acp_styles->main(0, ''); 36 | 37 | $this->qi_default_style = $settings->get_config('default_style', ''); 38 | 39 | // Get available styles 40 | $this->qi_get_styles(); 41 | 42 | // Install all styles. 43 | foreach ($this->qi_styles as $key => $style) 44 | { 45 | if (empty($this->qi_styles[$key]['style_id'])) 46 | { 47 | $this->qi_install_style($style); 48 | } 49 | } 50 | } 51 | 52 | private function qi_install_style($style) 53 | { 54 | global $phpbb_root_path; 55 | 56 | if (!empty($style['inherit_from']) && empty($this->qi_styles[$style['inherit_from']])) 57 | { 58 | // We don't have the parent skip this. 59 | return; 60 | } 61 | 62 | if (!empty($style['inherit_from']) && empty($this->qi_styles[$style['inherit_from']]['style_id'])) 63 | { 64 | // Need to install the parent first. 65 | $this->qi_install_style($this->qi_styles[$style['inherit_from']]); 66 | } 67 | 68 | $error = array(); 69 | 70 | $install_path = $style['install_path']; 71 | $root_path = "{$phpbb_root_path}styles/$install_path/"; 72 | 73 | $this->acp_styles->install_style($error, 'install', $root_path, $style['style_id'], $style['style_name'], $install_path, $style['style_copyright'], $style['style_active'], $style['style_default'], $style); 74 | unset($error); 75 | 76 | $this->qi_styles[$style['style_name']] = $style; 77 | } 78 | 79 | private function qi_get_styles() 80 | { 81 | $dh = dir($this->styles_path); 82 | while (($dir = $dh->read()) !== false) 83 | { 84 | // Ignore everything that starts with a dot, is a file or prosilver. 85 | if ($dir[0] === '.' || is_file($this->styles_path . $dir)) 86 | { 87 | continue; 88 | } 89 | 90 | $style_ary = array( 91 | 'style_id' => ($dir === 'prosilver') ? 1 : 0, 92 | 'template_id' => 0, 93 | 'theme_id' => 0, 94 | 'imageset_id' => 0, 95 | 'store_db' => 0, 96 | 'style_active' => 1, 97 | ); 98 | 99 | // Read cfg files and fill $style_ary. 100 | // style.cfg 101 | $install_path = $this->styles_path . $dir; 102 | 103 | $cfg_file = $install_path . '/style.cfg'; 104 | $rows = parse_cfg_file($cfg_file); 105 | $style_name = (!empty($rows['name'])) ? $rows['name'] : ''; 106 | 107 | $style_ary['style_name'] = (!empty($rows['name'])) ? $rows['name'] : ''; 108 | $style_ary['style_copyright'] = (!empty($rows['copyright'])) ? $rows['copyright'] : ''; 109 | 110 | // imageset.cfg 111 | $cfg_file = $install_path . '/imageset/imageset.cfg'; 112 | $rows = parse_cfg_file($cfg_file); 113 | 114 | $style_ary['imageset_name'] = (!empty($rows['name'])) ? $rows['name'] : ''; 115 | $style_ary['imageset_copyright'] = (!empty($rows['copyright'])) ? $rows['copyright'] : ''; 116 | 117 | // template.cfg 118 | $cfg_file = $install_path . '/template/template.cfg'; 119 | $rows = parse_cfg_file($cfg_file); 120 | 121 | $style_ary['template_name'] = (!empty($rows['name'])) ? $rows['name'] : ''; 122 | $style_ary['template_copyright'] = (!empty($rows['copyright'])) ? $rows['copyright'] : ''; 123 | $style_ary['bbcode_bitfield'] = (!empty($rows['template_bitfield'])) ? $rows['template_bitfield'] : ''; 124 | $style_ary['inherit_from'] = (!empty($rows['inherit_from'])) ? $rows['inherit_from'] : ''; 125 | 126 | // theme.cfg 127 | $cfg_file = $install_path . '/theme/theme.cfg'; 128 | $rows = parse_cfg_file($cfg_file); 129 | 130 | $style_ary['theme_name'] = (!empty($rows['name'])) ? $rows['name'] : ''; 131 | $style_ary['theme_copyright'] = (!empty($rows['copyright'])) ? $rows['copyright'] : ''; 132 | 133 | // Other stuff 134 | $style_ary['style_default'] = ($this->qi_default_style == $style_name) ? 1 : 0; 135 | $style_ary['install_path'] = $dir; 136 | 137 | $this->qi_styles[$style_name] = $style_ary; 138 | } 139 | $dh->close(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /includes/twig.php: -------------------------------------------------------------------------------- 1 | twig = new Environment($loader, [ 47 | 'cache' => $cachepath, 48 | 'autoescape' => false, 49 | 'auto_reload' => defined('QI_DEBUG'), 50 | ]); 51 | 52 | $this->twig->addFunction( 53 | new TwigFunction('lang', [$this, 'lang']) 54 | ); 55 | 56 | $this->user = $user; 57 | } 58 | 59 | /** 60 | * Set the path to the cache 61 | * 62 | * @param string $cachepath 63 | */ 64 | public function set_cachepath($cachepath) 65 | { 66 | if ($this->twig->getCache() !== $cachepath) 67 | { 68 | $this->twig->setCache($cachepath); 69 | } 70 | } 71 | 72 | /** 73 | * Render the template using Twig's render functionality 74 | * 75 | * @param string $templateFile Template file to load 76 | * @param string $templateExt Template file extension (twig, html, etc) 77 | * 78 | * @throws \Twig\Error\LoaderError 79 | * @throws \Twig\Error\RuntimeError 80 | * @throws \Twig\Error\SyntaxError 81 | */ 82 | public function display($templateFile, $templateExt = 'twig') 83 | { 84 | echo $this->twig->render("$templateFile.$templateExt", $this->variables); 85 | } 86 | 87 | /** 88 | * Assign a single scalar value to a single key. 89 | * 90 | * Value can be a string, an integer or boolean. 91 | * 92 | * @param string $varname Variable name 93 | * @param string $varval Value to assign to variable 94 | */ 95 | public function assign_var($varname, $varval) 96 | { 97 | $this->variables[$varname] = $varval; 98 | } 99 | 100 | /** 101 | * Assign key variable pairs from an array 102 | * 103 | * @param array $vararray A hash of variable name => value pairs 104 | */ 105 | 106 | public function assign_vars(array $vararray) 107 | { 108 | foreach ($vararray as $key => $value) 109 | { 110 | $this->assign_var($key, $value); 111 | } 112 | } 113 | 114 | /** 115 | * Append text to the string value stored in a key. 116 | * 117 | * Text is appended using the string concatenation operator (.). 118 | * 119 | * @param string $varname Variable name 120 | * @param string $varval Value to append to variable 121 | */ 122 | public function append_var($varname, $varval) 123 | { 124 | $this->variables[$varname] = (isset($this->variables[$varname]) ? $this->variables[$varname] : '') . $varval; 125 | } 126 | 127 | /** 128 | * Assign key variable pairs from an array to a specified block 129 | * 130 | * @param string $blockname Name of block to assign $vararray to 131 | * @param array $vararray A hash of variable name => value pairs 132 | */ 133 | public function assign_block_vars($blockname, array $vararray) 134 | { 135 | // For nested block, $blockcount > 0, for top-level block, $blockcount == 0 136 | $blocks = explode('.', $blockname); 137 | $blockcount = count($blocks) - 1; 138 | 139 | $block = &$this->variables; 140 | for ($i = 0; $i < $blockcount; $i++) 141 | { 142 | $pos = strpos($blocks[$i], '['); 143 | $name = ($pos !== false) ? substr($blocks[$i], 0, $pos) : $blocks[$i]; 144 | $block = &$block[$name]; 145 | $block_count = empty($block) ? 0 : count($block) - 1; 146 | $index = (!$pos || strpos($blocks[$i], '[]') === $pos) ? $block_count : (min((int) substr($blocks[$i], $pos + 1, -1), $block_count)); 147 | $block = &$block[$index]; 148 | } 149 | 150 | // $block = &$block[$blocks[$i]]; // Do not traverse the last block as it might be empty 151 | $name = $blocks[$i]; 152 | 153 | // Now we add the block that we're actually assigning to. 154 | // We're adding a new iteration to this block with the given 155 | // variable assignments. 156 | $block[$name][] = $vararray; 157 | } 158 | 159 | /** 160 | * Assign key variable pairs from an array to a whole specified block loop 161 | * 162 | * @param string $blockname Name of block to assign $block_vars_array to 163 | * @param array $block_vars_array An array of hashes of variable name => value pairs 164 | */ 165 | public function assign_block_vars_array($blockname, array $block_vars_array) 166 | { 167 | foreach ($block_vars_array as $vararray) 168 | { 169 | $this->assign_block_vars($blockname, $vararray); 170 | } 171 | } 172 | 173 | /** 174 | * Custom lang function returns a translated lang key from the template 175 | * 176 | * @return string 177 | */ 178 | public function lang() 179 | { 180 | $args = func_get_args(); 181 | $key = array_shift($args); 182 | 183 | return $this->lang_array($key, $args); 184 | } 185 | 186 | /** 187 | * Translate the language key. Perform substitution if args are provided. 188 | * 189 | * @param string $key Language key 190 | * @param array $args Optional arguments 191 | * @return string 192 | */ 193 | protected function lang_array($key, array $args = []) 194 | { 195 | if (!isset($this->user->lang[$key])) 196 | { 197 | return $key; 198 | } 199 | 200 | $lang = $this->user->lang[$key]; 201 | 202 | return count($args) ? vsprintf($lang, $args) : $lang; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # phpBB QuickInstall 2 | 3 | QuickInstall is a tool we built to support the community of phpBB extension developers (and previously MOD authors). It simplifies and accelerates the process of creating and configuring local phpBB3 forum installations. These boards can then be used to safely install, develop and test extensions in isolation without having to worry about external conflicts. 4 | 5 | > ##### ⚠️ QuickInstall is not intended for use on a live production website. 6 | > QuickInstall stores all board and database passwords in a plain text file. They are hidden in the user interface, but can be read by anyone with access to the QuickInstall directory. Therefore, if you do use QuickInstall on a public server, you do so at your own risk and must protect access to the directory where it resides from unauthorised users. No support is provided for QuickInstall other than local use. 7 | 8 | ## 📦 Installation 9 | 1. Get the latest version of [QuickInstall](https://www.phpbb.com/customise/db/official_tool/phpbb3_quickinstall/). 10 | 11 | 2. Extract it and copy the `quickinstall` folder to your local web server. 12 | 13 | 3. [Download a copy of phpBB3](https://www.phpbb.com/downloads/). Extract it and copy the `phpBB3` folder to `quickinstall/sources/`. 14 | 15 | 4. Point your web browser to the QuickInstall directory (`http://localhost/quickinstall` for instance) and follow the setup instructions. 16 | 17 | > **Alternate phpBB Profiles:**
18 | > You can store additional versions of phpBB and boards with alternate styles or language packs in `sources/phpBB3_alt/` . You can name these alternate phpBB folders whatever you want, e.g.: `sources/phpBB3_alt/phpBB-3.0.12`, `sources/phpBB3_alt/phpBB-sv`, etc. They will then be available as alternative phpBB3 boards you can choose to install or save as Profiles. [Past releases of phpBB can be downloaded here](https://download.phpbb.com/pub/release/). 19 | 20 | > **Adding phpBB Extras**:
21 | > If you want additional files/folders, such as extensions, to be copied to your boards when they are created, you can put them in the `sources/extra/` directory. By using the same directory structure in `sources/extra/` as phpBB, the files/folders should be mapped to the correct locations in your boards. For example: `sources/extra/ext/phpbb/pages`. 22 | 23 | ## 🛠 Upgrading 24 | 1. Get the latest version of [QuickInstall](https://www.phpbb.com/customise/db/official_tool/phpbb3_quickinstall/) and extract it. 25 | 26 | 2. Copy everything into your existing QuickInstall directory **except for the 📁`boards/`, 📁`sources/` and 📁`settings/` directories**. 27 | 28 | > If you are upgrading from QuickInstall 1.1.8 (or older) you MUST review and re-save your old Profile settings. 29 | 30 | ## 💻 Requirements 31 | 32 | ##### Browsers 33 | QuickInstall is designed to run on all modern browsers. Please don't use old stuff anymore...seriously. 34 | 35 | | | | | | | | 36 | |-|-|-|-|-|-| 37 | | Desktop: | ![Chrome](https://cdnjs.cloudflare.com/ajax/libs/browser-logos/69.0.4/chrome/chrome_32x32.png) 60+ | ![Firefox](https://cdnjs.cloudflare.com/ajax/libs/browser-logos/69.0.4/firefox/firefox_32x32.png) 60+ | ![Safari](https://cdnjs.cloudflare.com/ajax/libs/browser-logos/69.0.4/safari/safari_32x32.png) 12+ | ![Edge](https://cdnjs.cloudflare.com/ajax/libs/browser-logos/69.0.4/edge/edge_32x32.png) 80+ | ![Opera](https://cdnjs.cloudflare.com/ajax/libs/browser-logos/69.0.4/opera/opera_32x32.png) 40+ | 38 | | Mobile: | ![iOS](https://cdnjs.cloudflare.com/ajax/libs/browser-logos/69.0.4/safari-ios/safari-ios_32x32.png) 12+ | ![Android](https://cdnjs.cloudflare.com/ajax/libs/browser-logos/69.0.4/android-webview/android-webview_32x32.png) 6+ | | | | 39 | 40 | ##### phpBB Requirements 41 | phpBB boards require a web server running PHP and one of the following database management systems. 42 | 43 | | phpBB | PHP | MySQL | MariaDB | PostgreSQL | SQLite | MS SQL | 44 | |----------------|---------------|--------|---------|------------|----------------|--------------| 45 | | 4.0.0 (alpha) | 8.1+ | 4.1.3+ | 5.1+ | 8.3+ | SQLite 3.6.15+ | Server 2000+ | 46 | | 3.3.11 - 3.3.x | 7.2.0 - 8.x | 4.1.3+ | 5.1+ | 8.3+ | SQLite 3.6.15+ | Server 2000+ | 47 | | 3.3.0 - 3.3.10 | 7.1.3 - 8.x | 4.1.3+ | 5.1+ | 8.3+ | SQLite 3.6.15+ | Server 2000+ | 48 | | 3.2.2 - 3.2.11 | 5.4.7 - 7.2.x | 3.23+ | 5.1+ | 8.3+ | SQLite 3.6.15+ | Server 2000+ | 49 | | 3.2.0 - 3.2.1 | 5.4.7 - 7.1.x | 3.23+ | 5.1+ | 8.3+ | SQLite 3.6.15+ | Server 2000+ | 50 | | 3.1.x | 5.4.7 - 5.6.x | 3.23+ | 5.1+ | 8.3+ | SQLite 2 or 3 | Server 2000+ | 51 | | 3.0.x | 5.4.7 - 5.6.x | 3.23+ | - | 7.x | SQLite 2 | Server 2000 | 52 | 53 | ## 🐞 Support 54 | You can receive support at the [phpBB3 QuickInstall Discussion/Support](https://www.phpbb.com/customise/db/official_tool/phpbb3_quickinstall/support) forum. 55 | 56 | Please report all bugs to our [Issues Tracker](https://github.com/phpbb/quickinstall/issues). Even reports for small bugs are welcome to help make QuickInstall even better than it is now. 57 | 58 | ## 👋 Contributing 59 | Feel free to contribute to this project. Please read our [Contributing Guidelines](https://github.com/phpbb/quickinstall/blob/master/.github/CONTRIBUTING.md) before submitting Pull Requests with any bug fixes or feature enhancements to this repository. 60 | 61 | ## 💖 Credits 62 | The project is maintained by the phpBB Extensions Team. 63 | - Credits go to the phpBB team, especially the development team which created such a wonderful piece of software. 64 | - Originally created by Igor “igorw” Wiedler in the summer of 2007. 65 | - Maintained by Jari “tumba25” Kanerva from March 2010 to March 2015. 66 | - Thanks to the phpBB.com MOD team (especially Josh, aka “A_Jelly_Doughnut”) for AutoMOD. 67 | - Thanks to the beta testers! 68 | - Thanks to the phpBB community including phpBB.com, startrekguide.com and phpBBModders.net! 69 | 70 | ## 📜 License 71 | phpBB QuickInstall is distributed under the terms of the [GNU General Public License 2 (GPL)](license.txt). 72 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | = 50400) 35 | { 36 | // PHP 5.4 adds E_STRICT to E_ALL. 37 | // Our utf8 normalizer triggers E_STRICT output on PHP 5.4. 38 | // Unfortunately it cannot be made E_STRICT-clean while 39 | // continuing to work on PHP 4. 40 | // Therefore, in phpBB 3.0.x we disable E_STRICT on PHP 5.4+, 41 | // while phpBB 3.1 will fix utf8 normalizer. 42 | // E_STRICT is defined starting with PHP 5 43 | if (!defined('E_STRICT')) 44 | { 45 | define('E_STRICT', 2048); 46 | } 47 | $level &= ~E_STRICT; 48 | } 49 | 50 | if (PHP_VERSION_ID >= 50500) 51 | { 52 | // The /e modifier is deprecated as of PHP 5.5.0 according to php.net. 53 | // it is used in phpBB 3.0.x file: includes\functions_content.php 54 | // That is needed to work on PHP 4.x 55 | // It will be fixed in phpBB 3.1 56 | if (!defined('E_DEPRECATED')) 57 | { 58 | define('E_DEPRECATED', 8192); 59 | } 60 | $level &= ~E_DEPRECATED; 61 | } 62 | error_reporting($level); 63 | 64 | // If we are on PHP5 we may need to define STRIP to strip slashes 65 | define('STRIP', PHP_VERSION_ID < 60000 && get_magic_quotes_gpc()); 66 | 67 | // Try to override some limits - maybe it helps some... 68 | @set_time_limit(0); 69 | @ini_set('memory_limit', '128M'); 70 | 71 | // Set PHP error handler to ours 72 | set_error_handler(array('qi', 'msg_handler'), E_ALL); 73 | 74 | // Check PHP version 75 | if (PHP_VERSION_ID < 50407) 76 | { 77 | trigger_error('ERROR_PHP_UNSUPPORTED'); 78 | } 79 | 80 | // Make sure we have phpBB. 81 | if (!file_exists($quickinstall_path . 'sources/phpBB3/common.' . $phpEx)) 82 | { 83 | trigger_error('ERROR_PHPBB_NOT_FOUND'); 84 | } 85 | 86 | // Let's get the config. 87 | $page = qi_request_var('page', 'main'); 88 | $mode = qi_request_var('mode', ''); 89 | $profile = qi_request_var('qi_profile', ''); 90 | 91 | $settings = new settings($quickinstall_path); 92 | 93 | // delete settings profile if requested 94 | if (qi_request_var('delete-profile', false) !== false) 95 | { 96 | $settings->delete_profile($profile); 97 | $profile = ''; 98 | } 99 | 100 | // delete all phpbb cookies if requested 101 | if (qi_request_var('delete-cookies', '') === qi::PHPBB_COOKIE_PREFIX) 102 | { 103 | qi::delete_cookies(qi::PHPBB_COOKIE_PREFIX); 104 | } 105 | 106 | // load settings profile 107 | $settings->import_profile($profile); 108 | 109 | // We need some phpBB functions too. 110 | $alt_env = $settings->get_config('alt_env', ''); 111 | $alt_env_missing = false; 112 | if ($alt_env !== '') 113 | { 114 | if (file_exists("{$quickinstall_path}sources/phpBB3_alt/$alt_env/") && is_dir("{$quickinstall_path}sources/phpBB3_alt/$alt_env/")) 115 | { 116 | $phpbb_root_path = "{$quickinstall_path}sources/phpBB3_alt/$alt_env/"; 117 | } 118 | else 119 | { 120 | $alt_env_missing = true; 121 | } 122 | } 123 | 124 | $phpbb_version = get_phpbb_version($phpbb_root_path); 125 | 126 | if (file_exists($phpbb_root_path . 'phpbb/class_loader.' . $phpEx)) 127 | { 128 | define('PHPBB_31', true); 129 | 130 | require($phpbb_root_path . 'phpbb/class_loader.' . $phpEx); 131 | $phpbb_class_loader = new \phpbb\class_loader('phpbb\\', $phpbb_root_path . 'phpbb/', $phpEx); 132 | $phpbb_class_loader->register(); 133 | 134 | require($phpbb_root_path . 'vendor/autoload.' . $phpEx); 135 | } 136 | 137 | if (!file_exists($phpbb_root_path . 'includes/functions_install.' . $phpEx) || version_compare($phpbb_version, '3.2', '>=')) 138 | { 139 | define('PHPBB_32', true); 140 | } 141 | 142 | if (file_exists($phpbb_root_path . 'vendor-ext') || version_compare($phpbb_version, '4.0', '>=')) 143 | { 144 | define('PHPBB_40', true); 145 | } 146 | 147 | require($phpbb_root_path . 'includes/functions.' . $phpEx); 148 | require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); 149 | 150 | if (!function_exists('phpbb_email_hash') || version_compare($phpbb_version, '3.3', '>=')) 151 | { 152 | define('PHPBB_33', true); 153 | } 154 | 155 | // Need to set prefix here before constants.php are included. 156 | $table_prefix = $settings->get_config('table_prefix', ''); 157 | 158 | require($phpbb_root_path . 'includes/constants.' . $phpEx); 159 | 160 | if (!qi::phpbb_branch('3.2')) 161 | { 162 | require($phpbb_root_path . 'includes/functions_install.' . $phpEx); 163 | } 164 | 165 | if (qi::phpbb_branch('3.1')) 166 | { 167 | if (qi::phpbb_branch('3.2')) 168 | { 169 | $cache_dir = $quickinstall_path . $settings->get_config('cache_dir', ''); 170 | $cache = new \phpbb\cache\driver\file($cache_dir); 171 | $user = new \phpbb\user( 172 | new \phpbb\language\language( 173 | new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx)), 174 | '\phpbb\datetime' 175 | ); 176 | } 177 | else 178 | { 179 | $user = new \phpbb\user('\phpbb\datetime'); 180 | $cache = new \phpbb\cache\driver\file(); 181 | } 182 | 183 | $auth = new \phpbb\auth\auth(); 184 | require($phpbb_root_path . 'includes/functions_compatibility.' . $phpEx); 185 | } 186 | else 187 | { 188 | require($phpbb_root_path . 'includes/acm/acm_file.' . $phpEx); 189 | require($phpbb_root_path . 'includes/auth.' . $phpEx); 190 | require($phpbb_root_path . 'includes/cache.' . $phpEx); 191 | require($phpbb_root_path . 'includes/session.' . $phpEx); 192 | 193 | // Create the user. 194 | $user = new user(); 195 | $auth = new auth(); 196 | $cache = new cache(); 197 | } 198 | 199 | // We need to set the template here. 200 | $template = new twig($user, $settings->get_cache_dir(), $quickinstall_path); 201 | 202 | // If there is a language selected in the dropdown menu in settings it's sent as GET, then igonre the hidden POST field. 203 | if (isset($_GET['lang'])) 204 | { 205 | $language = qi_request_var('lang', ''); 206 | } 207 | else if (!empty($_POST['sel_lang'])) 208 | { 209 | $language = qi_request_var('sel_lang', ''); 210 | } 211 | else 212 | { 213 | $language = $settings->get_config('qi_lang', ''); 214 | } 215 | qi::apply_lang($language); 216 | 217 | $profiles = $settings->get_profiles(); 218 | 219 | // Probably best place to validate the settings 220 | $settings->validate(); 221 | $errors = $settings->get_errors(); 222 | 223 | // Set some standard variables we want to force 224 | if (qi::phpbb_branch('3.1')) 225 | { 226 | $config = new \phpbb\config\config(array( 227 | 'load_tplcompile' => '1', 228 | )); 229 | qi_set_config(false, false, false, $config); 230 | qi_set_config_count(null, null, null, $config); 231 | } 232 | else 233 | { 234 | $config = array( 235 | 'load_tplcompile' => '1', 236 | ); 237 | } 238 | 239 | // update cache path 240 | $template->set_cachepath($settings->get_cache_dir()); 241 | 242 | // force going to the settings page 243 | if (!empty($errors) || $alt_env_missing || (empty($profiles) && ($page === 'main' || $page === '')) || ($page === 'main' && $settings->is_install())) 244 | { 245 | $page = 'settings'; 246 | } 247 | 248 | // Hide manage boards if there is no saved config. 249 | $template->assign_var('S_IN_INSTALL', $settings->is_install()); 250 | $template->assign_var('S_HAS_PROFILES', $profiles); 251 | 252 | // now create a qi_module object 253 | $module = new qi_module($quickinstall_path . 'modules/', 'qi_'); 254 | 255 | // Load the main module 256 | $module->load($page, 'qi_main'); 257 | -------------------------------------------------------------------------------- /style/assets/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function(document, window) { 2 | 'use strict'; 3 | 4 | const ready = fn => { 5 | if (document.readyState === 'complete') { 6 | return fn(); 7 | } 8 | 9 | document.addEventListener('DOMContentLoaded', fn, false); 10 | }; 11 | 12 | ready(() => { 13 | // Create form validation and submit 14 | const $form = $('.needs-validation'); 15 | if ($form) { 16 | $('button[type="submit"]', $form).addEventListener('click', event => { 17 | event.preventDefault(); 18 | let validated = true; 19 | for (const $input of $$('input[required]')) { 20 | const empty = $input.value === ''; 21 | if (empty) { 22 | validated = false; 23 | } 24 | 25 | if (!empty && $input.validity.valid) { 26 | $input.classList.add('is-valid'); 27 | } else { 28 | $input.classList.add('is-invalid'); 29 | } 30 | } 31 | 32 | if (validated) { 33 | if ($form.getAttribute('data-qi-submit-ajax') === undefined) { 34 | $form.submit(); 35 | } else { 36 | ajaxSubmit($form); 37 | } 38 | } 39 | }); 40 | } 41 | 42 | // Submit form via ajax 43 | const ajaxSubmit = $form => { 44 | const modal = $('[data-qi-submit-modal]'); 45 | const $modal = new bootstrap.Modal(modal, { 46 | keyboard: false, 47 | backdrop: 'static', 48 | }); 49 | modal.addEventListener('shown.bs.modal', () => { 50 | const xhr = new XMLHttpRequest(); 51 | 52 | xhr.responseType = 'json'; 53 | xhr.addEventListener('loadend', () => { 54 | if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { 55 | if (typeof xhr.response.redirect !== 'undefined' && xhr.response.redirect) { 56 | window.location.replace(xhr.response.redirect); 57 | } else if (typeof xhr.response.responseText !== 'undefined' && xhr.response.responseText) { 58 | mainAlert(xhr.response.responseText); 59 | } 60 | } else if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 500) { 61 | mainAlert(xhr.statusText); 62 | } 63 | 64 | $modal.hide(); 65 | window.scrollTo(0, 0); 66 | }); 67 | 68 | xhr.open('POST', $form.getAttribute('action').replace('&', '&')); 69 | xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 70 | xhr.send(new FormData($form)); 71 | }); 72 | $modal.show(); 73 | }; 74 | 75 | // Submit forms on change 76 | for (const $select of $$('[data-qi-form-submit=true]')) { 77 | $select.addEventListener('change', event => { 78 | event.target.closest('form').submit(); 79 | }); 80 | } 81 | 82 | // Toggle all checkboxes 83 | const $toggleAll = $('[data-qi-mark-list]'); 84 | if ($toggleAll) { 85 | const $targetForm = $toggleAll.closest('form'); 86 | const $checkboxes = $$('[data-qi-mark]', $targetForm); 87 | $toggleAll.addEventListener('change', () => { 88 | for (const box of $checkboxes) { 89 | box.checked = $toggleAll.checked; 90 | } 91 | }); 92 | for (const box of $checkboxes) { 93 | box.addEventListener('change', event => { 94 | const $check = event.target; 95 | if ($check.checked === false) { 96 | $toggleAll.checked = false; 97 | return; 98 | } 99 | 100 | if ($$('[data-qi-mark]:checked', $targetForm).length === $checkboxes.length) { 101 | $toggleAll.checked = true; 102 | } 103 | }); 104 | } 105 | } 106 | 107 | // Confirm alert dialog 108 | for (const $confirmDelete of $$('[data-qi-confirm]')) { 109 | $confirmDelete.addEventListener('click', event => { 110 | const message = $confirmDelete.getAttribute('data-qi-confirm'); 111 | if (!confirm(message)) { 112 | event.preventDefault(); 113 | } 114 | }); 115 | } 116 | 117 | // Load new page from menu selection 118 | const $loadSelection = $('[data-qi-load-selection]'); 119 | if ($loadSelection) { 120 | $loadSelection.addEventListener('change', () => { 121 | const url = $loadSelection.getAttribute('data-qi-load-selection'); 122 | const iso = $loadSelection.querySelector(':checked').value; 123 | window.location.href = url + iso; 124 | }); 125 | } 126 | 127 | // Show config 128 | const $config = $('#config_text_button'); 129 | if ($config) { 130 | $config.addEventListener('click', () => { 131 | $('#config_text_alert').style.display = 'none'; 132 | $('#config_text_container').classList.remove('d-none'); 133 | }); 134 | } 135 | 136 | // Copy data from a textarea field 137 | const $configField = $('[data-qi-copy]'); 138 | if ($configField) { 139 | $configField.addEventListener('click', event => { 140 | const target = '#' + event.target.getAttribute('data-qi-copy'); 141 | $(target).select(); 142 | document.execCommand('copy'); 143 | }); 144 | } 145 | 146 | // Search filter for PHP info table 147 | const $phpinfo = $('#phpinfo-filter'); 148 | if ($phpinfo) { 149 | $phpinfo.addEventListener('keyup', event => { 150 | const regex = new RegExp(event.target.value, 'i'); 151 | for (const row of $$('.searchable tr')) { 152 | row.style.display = 'none'; 153 | } 154 | 155 | const filtered = Array.prototype.filter.call($$('.searchable tr'), node => { 156 | const text = node.textContent || node.innerText; 157 | return regex.test(text); 158 | }); 159 | for (const row of filtered) { 160 | row.style.display = 'table-row'; 161 | } 162 | }); 163 | } 164 | 165 | // Database connection test 166 | const $testDbBtn = $('#test-db-connection'); 167 | if ($testDbBtn) { 168 | $testDbBtn.addEventListener('click', () => { 169 | const $result = $('#db-test-result'); 170 | $testDbBtn.disabled = true; 171 | $testDbBtn.innerHTML = ' Testing...'; 172 | 173 | const formData = new FormData(); 174 | formData.append('dbms', $('#dbms').value); 175 | formData.append('dbhost', $('#dbhost').value); 176 | formData.append('dbport', $('#dbport').value); 177 | formData.append('dbuser', $('#dbuser').value); 178 | formData.append('dbpasswd', $('#dbpasswd').value); 179 | 180 | const xhr = new XMLHttpRequest(); 181 | xhr.responseType = 'json'; 182 | xhr.addEventListener('loadend', () => { 183 | $testDbBtn.disabled = false; 184 | $testDbBtn.innerHTML = ' Test Database Connection'; 185 | 186 | if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { 187 | const response = xhr.response; 188 | if (response.success) { 189 | $result.className = 'mt-2 alert alert-success'; 190 | $result.innerHTML = ' ' + response.message; 191 | } else { 192 | $result.className = 'mt-2 alert alert-danger'; 193 | $result.innerHTML = ' ' + response.message; 194 | } 195 | } else { 196 | $result.className = 'mt-2 alert alert-danger'; 197 | $result.innerHTML = ' Connection test failed'; 198 | } 199 | }); 200 | 201 | xhr.open('POST', 'index.php?page=settings&mode=test_db_connection'); 202 | xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 203 | xhr.send(formData); 204 | }); 205 | } 206 | 207 | // Notification of QI update (use sessionStorage for dismissed notification) 208 | if (sessionStorage.getItem('qiupdate') === null) { 209 | const qiUpdateToast = $('#qiUpdateToast'); 210 | if (qiUpdateToast) { 211 | const toast = new bootstrap.Toast(qiUpdateToast, { 212 | autohide: false, 213 | }); 214 | toast.show(); 215 | qiUpdateToast.addEventListener('hidden.bs.toast', () => { 216 | sessionStorage.setItem('qiupdate', '1'); 217 | }); 218 | } 219 | } 220 | }); 221 | 222 | const mainAlert = text => { 223 | $('#main-alert > p').innerHTML = text; 224 | $('#main-alert').classList.remove('d-none'); 225 | }; 226 | 227 | // Select a list of matching elements, context is optional 228 | function $$(selector, context) { 229 | return (context || document).querySelectorAll(selector); 230 | } 231 | 232 | // Select the first matching element, context is optional 233 | function $(selector, context) { 234 | return (context || document).querySelector(selector); 235 | } 236 | })(document, window); 237 | -------------------------------------------------------------------------------- /modules/qi_settings.php: -------------------------------------------------------------------------------- 1 | test_db_connection(); 23 | return; 24 | } 25 | else if ($mode === 'update_settings') 26 | { 27 | $qi_config = @utf8_normalize_nfc(qi_request_var('qi_config', array('' => ''), true)); 28 | 29 | $profile = $settings->set_settings($qi_config); 30 | 31 | if ($settings->validate()) 32 | { 33 | if (is_writable($quickinstall_path . 'settings')) 34 | { 35 | if ($settings->save_profile()) 36 | { 37 | $settings->set_profile_cookie($profile); 38 | $saved = true; 39 | } 40 | else 41 | { 42 | $errors[] = qi::lang('CONFIG_NOT_WRITTEN', $profile); 43 | $errors[] = qi::lang('CONFIG_IS_DISPLAYED'); 44 | $config_text = $settings->get_config_text(); 45 | } 46 | } 47 | else 48 | { 49 | $errors[] = qi::lang('CONFIG_NOT_WRITABLE'); 50 | $errors[] = qi::lang('CONFIG_IS_DISPLAYED'); 51 | $config_text = $settings->get_config_text(); 52 | } 53 | } 54 | else 55 | { 56 | $errors = $settings->get_errors(); 57 | } 58 | } 59 | else if ($alt_env_missing) 60 | { 61 | $errors[] = qi::lang('NO_ALT_ENV_FOUND', $alt_env); 62 | } 63 | 64 | $template->assign_vars(array( 65 | 'S_BOARDS_WRITABLE' => is_writable($settings->get_boards_dir()), 66 | 'S_CACHE_WRITABLE' => is_writable($settings->get_cache_dir()), 67 | 'S_CONFIG_WRITABLE' => is_writable($quickinstall_path . 'settings'), 68 | 'S_HAS_PROFILES' => $settings->get_profiles(), 69 | 'S_IN_INSTALL' => $settings->is_install(), 70 | 'S_SETTINGS_SUCCESS' => $saved && empty($errors), 71 | 'S_SETTINGS_ERRORS' => $errors, 72 | 'S_SETTINGS' => true, 73 | 74 | 'U_UPDATE_SETTINGS' => qi::url('settings', array('mode' => 'update_settings')), 75 | 'U_CHOOSE_PROFILE' => qi::url('settings', array('mode' => 'change_profile')), 76 | 77 | 'SAVE_PROFILE' => $settings->get_profile(), 78 | 'TABLE_PREFIX' => $settings->get_config('table_prefix', ''), 79 | 'SITE_NAME' => $settings->get_config('site_name', ''), 80 | 'SITE_DESC' => $settings->get_config('site_desc', ''), 81 | 'ALT_ENV' => !empty($alt_env) ? $alt_env : false, 82 | 'PROFILES' => $settings->get_profiles(), 83 | 'QI_LANG' => qi::get_lang_select($quickinstall_path . 'language/', 'qi_lang', 'lang'), 84 | 'PHPBB_LANG' => qi::get_lang_select($phpbb_root_path . '/language/', 'default_lang'), 85 | 86 | 'CONFIG_TEXT' => htmlspecialchars($config_text), 87 | 88 | // Config settings 89 | 'CHUNK_POST' => $settings->get_config('chunk_post', 0), 90 | 'CHUNK_TOPIC' => $settings->get_config('chunk_topic', 0), 91 | 'CHUNK_USER' => $settings->get_config('chunk_user', 0), 92 | 'CONFIG_ADMIN_EMAIL' => $settings->get_config('admin_email', ''), 93 | 'CONFIG_ADMIN_NAME' => $settings->get_config('admin_name', ''), 94 | 'CONFIG_ADMIN_PASS' => $settings->get_config('admin_pass', ''), 95 | 'CONFIG_ALT_ENV' => get_alternative_env($settings->get_config('alt_env', '')), 96 | 'CONFIG_BOARD_EMAIL' => $settings->get_config('board_email', ''), 97 | 'CONFIG_BOARDS_DIR' => $settings->get_boards_dir(), 98 | 'CONFIG_BOARDS_URL' => $settings->get_boards_url(), 99 | 'CONFIG_CACHE_DIR' => $settings->get_cache_dir(), 100 | 'CONFIG_COOKIE_DOMAIN' => $settings->get_config('cookie_domain', ''), 101 | 'CONFIG_COOKIE_SECURE' => $settings->get_config('cookie_secure', 0), 102 | 'CONFIG_DB_PREFIX' => $settings->get_config('db_prefix', ''), 103 | 'CONFIG_DBHOST' => $settings->get_config('dbhost', ''), 104 | 'CONFIG_DBMS' => gen_dbms_options($settings->get_config('dbms', '')), 105 | 'CONFIG_DBPASSWD' => $settings->get_config('dbpasswd', ''), 106 | 'CONFIG_DBPORT' => $settings->get_config('dbport', ''), 107 | 'CONFIG_DBUSER' => $settings->get_config('dbuser', ''), 108 | 'CONFIG_DEFAULT_STYLE' => $settings->get_config('default_style', ''), 109 | 'CONFIG_DELETE_FILES' => $settings->get_config('delete_files', 0), 110 | 'CONFIG_DEBUG' => $settings->get_config('debug', 0), 111 | 'CONFIG_DROP_DB' => $settings->get_config('drop_db', 0), 112 | 'CONFIG_EMAIL_ENABLE' => $settings->get_config('email_enable', 0), 113 | 'CONFIG_GRANT_PERMISSIONS' => $settings->get_config('grant_permissions', ''), 114 | 'CONFIG_INSTALL_STYLES' => $settings->get_config('install_styles', 0), 115 | 'CONFIG_MAKE_WRITABLE' => $settings->get_config('make_writable', 0), 116 | 'CONFIG_NO_PASSWORD' => $settings->get_config('no_dbpasswd', 0), 117 | 'CONFIG_POPULATE' => $settings->get_config('populate', 0), 118 | 'CONFIG_QI_DST' => $settings->get_config('qi_dst', 0), 119 | 'CONFIG_QI_TZ' => $settings->get_config('qi_tz', ''), 120 | 'CONFIG_REDIRECT' => $settings->get_config('redirect', 0), 121 | 'CONFIG_SERVER_NAME' => $settings->get_config('server_name', ''), 122 | 'CONFIG_SERVER_PORT' => $settings->get_config('server_port', ''), 123 | 'CONFIG_SITE_DESC' => $settings->get_config('site_desc', ''), 124 | 'CONFIG_SITE_NAME' => $settings->get_config('site_name', ''), 125 | 'CONFIG_SMTP_AUTH' => $settings->get_config('smtp_auth', ''), 126 | 'CONFIG_SMTP_DELIVERY' => $settings->get_config('smtp_delivery', 0), 127 | 'CONFIG_SMTP_HOST' => $settings->get_config('smtp_host', ''), 128 | 'CONFIG_SMTP_PASS' => $settings->get_config('smtp_pass', ''), 129 | 'CONFIG_SMTP_PORT' => $settings->get_config('smtp_port', 0), 130 | 'CONFIG_SMTP_USER' => $settings->get_config('smtp_user', ''), 131 | 'CONFIG_TABLE_PREFIX' => $settings->get_config('table_prefix', ''), 132 | 'CONFIG_NUM_USERS' => $settings->get_config('num_users', 0), 133 | 'CONFIG_NUM_NEW_GROUP' => $settings->get_config('num_new_group', 0), 134 | 'CONFIG_CREATE_ADMIN' => $settings->get_config('create_admin', 0), 135 | 'CONFIG_CREATE_MOD' => $settings->get_config('create_mod', 0), 136 | 'CONFIG_NUM_CATS' => $settings->get_config('num_cats', 0), 137 | 'CONFIG_NUM_FORUMS' => $settings->get_config('num_forums', 0), 138 | 'CONFIG_NUM_TOPICS_MIN' => $settings->get_config('num_topics_min', 0), 139 | 'CONFIG_NUM_TOPICS_MAX' => $settings->get_config('num_topics_max', 0), 140 | 'CONFIG_NUM_REPLIES_MIN'=> $settings->get_config('num_replies_min', 0), 141 | 'CONFIG_NUM_REPLIES_MAX'=> $settings->get_config('num_replies_max', 0), 142 | 'CONFIG_EMAIL_DOMAIN' => $settings->get_config('email_domain', ''), 143 | 144 | 'TIMEZONE_OPTIONS' => qi_timezone_select($user, $settings->get_config('qi_tz', 'UTC')), 145 | 146 | 'OTHER_CONFIG' => $settings->get_config('other_config', ''), 147 | 148 | 'SEL_LANG' => empty($errors) ? $settings->get_config('qi_lang', 'en') : '', 149 | )); 150 | 151 | // Output page 152 | qi::page_header('PROFILES'); 153 | 154 | qi::page_display('settings_body'); 155 | } 156 | 157 | private function test_db_connection() 158 | { 159 | header('Content-Type: application/json'); 160 | 161 | $dbms = qi_request_var('dbms', ''); 162 | $dbhost = qi_request_var('dbhost', ''); 163 | $dbport = qi_request_var('dbport', ''); 164 | $dbuser = qi_request_var('dbuser', ''); 165 | $dbpasswd = qi_request_var('dbpasswd', ''); 166 | 167 | if (empty($dbms)) 168 | { 169 | echo json_encode(['success' => false, 'message' => qi::lang('DB_TEST_TYPE_REQUIRED')]); 170 | return; 171 | } 172 | 173 | // SQLite doesn't use server connections, just test if extension is available 174 | if (in_array($dbms, ['sqlite', 'sqlite3'])) 175 | { 176 | try 177 | { 178 | if ($dbms === 'sqlite3') 179 | { 180 | new \SQLite3(':memory:'); 181 | echo json_encode(['success' => true, 'message' => qi::lang('DB_TEST_SQLITE3_AVAILABLE')]); 182 | } 183 | else 184 | { 185 | $error = null; 186 | @sqlite_open(':memory:', 0666, $error); 187 | echo json_encode(['success' => true, 'message' => qi::lang('DB_TEST_SQLITE_AVAILABLE')]); 188 | } 189 | } 190 | catch (Exception $e) 191 | { 192 | echo json_encode(['success' => false, 'message' => qi::lang('DB_TEST_SQLITE_NOT_AVAILABLE')]); 193 | } 194 | return; 195 | } 196 | 197 | if (empty($dbhost)) 198 | { 199 | echo json_encode(['success' => false, 'message' => qi::lang('DB_TEST_HOST_REQUIRED')]); 200 | return; 201 | } 202 | 203 | // we need to capture trigger_error() calls to be able to continue 204 | set_error_handler(function() { 205 | return true; 206 | }); 207 | 208 | try 209 | { 210 | $db_data = [$dbms, $dbhost, $dbuser, $dbpasswd, $dbport, '']; 211 | $db = db_connect($db_data); 212 | 213 | if ($db && $db->db_connect_id) 214 | { 215 | $db->sql_close(); 216 | restore_error_handler(); 217 | echo json_encode(['success' => true, 'message' => qi::lang('DB_TEST_CONNECTION_SUCCESS')]); 218 | } 219 | else 220 | { 221 | restore_error_handler(); 222 | echo json_encode(['success' => false, 'message' => qi::lang('DB_TEST_CONNECTION_FAILED')]); 223 | } 224 | } 225 | catch (Exception $e) 226 | { 227 | restore_error_handler(); 228 | echo json_encode(['success' => false, 'message' => qi::lang('DB_TEST_CONNECTION_FAILED')]); 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /includes/functions_install.php: -------------------------------------------------------------------------------- 1 | get(get_db_doctrine()); 54 | } 55 | 56 | if (qi::phpbb_branch('3.2')) 57 | { 58 | $factory = new \phpbb\db\tools\factory(); 59 | return $factory->get($db); 60 | } 61 | 62 | return new \phpbb\db\tools($db); 63 | } 64 | 65 | function load_schema($install_path = '', $install_dbms = false) 66 | { 67 | if (qi::phpbb_branch('3.1')) 68 | { 69 | load_schema_31($install_path, $install_dbms); 70 | } 71 | else 72 | { 73 | load_schema_30($install_path, $install_dbms); 74 | } 75 | } 76 | 77 | function load_schema_31($install_path = '', $install_dbms = false) 78 | { 79 | global $db, $settings, $table_prefix, $phpbb_root_path, $phpEx; 80 | 81 | static $available_dbms = false; 82 | 83 | if ($install_dbms === false) 84 | { 85 | $dbms = $settings->get_config('dbms', ''); 86 | $install_dbms = $dbms; 87 | } 88 | 89 | if (!$available_dbms) 90 | { 91 | $available_dbms = qi_get_available_dbms($install_dbms); 92 | 93 | // If mysql is chosen, we need to adjust the schema filename slightly to reflect the correct version. ;) 94 | if ($install_dbms == 'mysql') 95 | { 96 | if (version_compare($db->sql_server_info(true), '4.1.3', '>=')) 97 | { 98 | $available_dbms[$install_dbms]['SCHEMA'] .= '_41'; 99 | } 100 | else 101 | { 102 | $available_dbms[$install_dbms]['SCHEMA'] .= '_40'; 103 | } 104 | } 105 | } 106 | 107 | // Ok we have the db info go ahead and read in the relevant schema 108 | // and work on building the table 109 | $dbms_schema = $install_path . $available_dbms[$install_dbms]['SCHEMA'] . '_schema.sql'; 110 | 111 | // How should we treat this schema? 112 | $delimiter = $available_dbms[$install_dbms]['DELIM']; 113 | 114 | if (file_exists($dbms_schema)) 115 | { 116 | $sql_query = file_get_contents($dbms_schema); 117 | $sql_query = preg_replace('#phpbb_#i', $table_prefix, $sql_query); 118 | $sql_query = qi_remove_comments($sql_query); 119 | $sql_query = qi_split_sql_file($sql_query, $delimiter); 120 | 121 | foreach ($sql_query as $sql) 122 | { 123 | $db->sql_query($sql); 124 | } 125 | unset($sql_query); 126 | } 127 | 128 | // Ok we have the db info go ahead and work on building the table 129 | if (file_exists($install_path . 'schema.json')) 130 | { 131 | $db_table_schema = file_get_contents($install_path . 'schema.json'); 132 | $db_table_schema = json_decode($db_table_schema, true); 133 | } 134 | else 135 | { 136 | $table_prefix = 'phpbb_'; 137 | 138 | if (!defined('CONFIG_TABLE')) 139 | { 140 | // We need to include the constants file for the table constants 141 | // when we generate the schema from the migration files. 142 | include($phpbb_root_path . 'includes/constants.' . $phpEx); 143 | } 144 | 145 | if (qi::phpbb_branch('4.0')) 146 | { 147 | $finder = new \phpbb\finder\finder(null, true, $phpbb_root_path, $phpEx); 148 | } 149 | else 150 | { 151 | $finder = new \phpbb\finder(new \phpbb\filesystem(), $phpbb_root_path, null, $phpEx); 152 | } 153 | 154 | $classes = $finder->core_path('phpbb/db/migration/data/') 155 | ->get_classes(); 156 | 157 | if (!file_exists($phpbb_root_path . 'phpbb\db\driver\sqlite.' . $phpEx)) 158 | { 159 | $sqlite_db = new \phpbb\db\driver\sqlite3(); 160 | } 161 | else 162 | { 163 | $sqlite_db = new \phpbb\db\driver\sqlite(); 164 | } 165 | 166 | $db_tools = get_db_tools($sqlite_db); 167 | 168 | $args = array($classes, new \phpbb\config\config(array()), $sqlite_db, $db_tools, $phpbb_root_path, $phpEx, $table_prefix); 169 | if (qi::phpbb_branch('4.0')) 170 | { 171 | $tables_data = \Symfony\Component\Yaml\Yaml::parseFile($phpbb_root_path . '/config/default/container/tables.yml'); 172 | $tables = []; 173 | 174 | foreach ($tables_data['parameters'] as $parameter => $table) 175 | { 176 | $tables[str_replace('tables.', '', $parameter)] = str_replace('%core.table_prefix%', $table_prefix, $table); 177 | } 178 | 179 | $args[] = $tables; 180 | } 181 | 182 | $class = new ReflectionClass('\\phpbb\\db\\migration\\schema_generator'); 183 | $schema_generator = $class->newInstanceArgs($args); 184 | 185 | $db_table_schema = $schema_generator->get_schema(); 186 | } 187 | 188 | if (!defined('CONFIG_TABLE')) 189 | { 190 | // CONFIG_TABLE is required by sql_create_index() to check the 191 | // length of index names. However table_prefix is not defined 192 | // here yet, so we need to create the constant ourselves. 193 | define('CONFIG_TABLE', $table_prefix . 'config'); 194 | } 195 | 196 | $db_tools = get_db_tools($db); 197 | if (qi::phpbb_branch('4.0')) 198 | { 199 | $db_tools->set_table_prefix($table_prefix); 200 | } 201 | foreach ($db_table_schema as $table_name => $table_data) 202 | { 203 | $db_tools->sql_create_table( 204 | $table_prefix . substr($table_name, 6), 205 | $table_data 206 | ); 207 | } 208 | 209 | // Ok tables have been built, let's fill in the basic information 210 | $sql_query = file_get_contents($install_path . 'schema_data.sql'); 211 | 212 | // Deal with any special comments and characters 213 | switch ($install_dbms) 214 | { 215 | case 'mssql': 216 | case 'mssql_odbc': 217 | case 'mssqlnative': 218 | $sql_query = preg_replace('#\# MSSQL IDENTITY (phpbb_[a-z_]+) (ON|OFF) \##s', 'SET IDENTITY_INSERT \1 \2;', $sql_query); 219 | break; 220 | 221 | case 'postgres': 222 | $sql_query = preg_replace('#\# POSTGRES (BEGIN|COMMIT) \##s', '\1; ', $sql_query); 223 | break; 224 | 225 | case 'mysql': 226 | case 'mysqli': 227 | $sql_query = str_replace('\\', '\\\\', $sql_query); 228 | break; 229 | } 230 | 231 | // Change prefix 232 | $sql_query = preg_replace('# phpbb_([^\s]*) #i', ' ' . $table_prefix . '\1 ', $sql_query); 233 | 234 | // Change language strings... 235 | $sql_query = preg_replace_callback('#\{L_([A-Z0-9\-_]*)\}#s', 'qi_adjust_language_keys_callback', $sql_query); 236 | 237 | $sql_query = qi_remove_comments($sql_query); 238 | $sql_query = qi_split_sql_file($sql_query, ';'); 239 | 240 | foreach ($sql_query as $sql) 241 | { 242 | $db->sql_query($sql); 243 | } 244 | unset($sql_query); 245 | } 246 | 247 | /** 248 | * Load a schema (and execute) 249 | * 250 | * @param string $install_path 251 | */ 252 | function load_schema_30($install_path = '', $install_dbms = false) 253 | { 254 | global $settings, $db, $table_prefix; 255 | 256 | static $available_dbms = false; 257 | 258 | if ($install_dbms === false) 259 | { 260 | $dbms = $settings->get_config('dbms', ''); 261 | $install_dbms = $dbms; 262 | } 263 | 264 | if (!function_exists('get_available_dbms') && !qi::phpbb_branch('3.2')) 265 | { 266 | global $phpbb_root_path, $phpEx; 267 | include($phpbb_root_path . 'includes/functions_install.' . $phpEx); 268 | } 269 | 270 | if (!$available_dbms) 271 | { 272 | $available_dbms = qi_get_available_dbms($install_dbms); 273 | 274 | if ($install_dbms == 'mysql') 275 | { 276 | if (version_compare($db->mysql_version, '4.1.3', '>=')) 277 | { 278 | $available_dbms[$install_dbms]['SCHEMA'] .= '_41'; 279 | } 280 | else 281 | { 282 | $available_dbms[$install_dbms]['SCHEMA'] .= '_40'; 283 | } 284 | } 285 | } 286 | 287 | $remove_remarks = $available_dbms[$install_dbms]['COMMENTS']; 288 | $delimiter = $available_dbms[$install_dbms]['DELIM']; 289 | 290 | $dbms_schema = $install_path . $available_dbms[$install_dbms]['SCHEMA'] . '_schema.sql'; 291 | 292 | if (file_exists($dbms_schema)) 293 | { 294 | $sql_query = @file_get_contents($dbms_schema); 295 | 296 | $sql_query = preg_replace('#phpbb_#i', $table_prefix, $sql_query); 297 | 298 | $remove_remarks($sql_query); 299 | 300 | $sql_query = qi_split_sql_file($sql_query, $delimiter); 301 | 302 | foreach ($sql_query as $sql) 303 | { 304 | $db->sql_query($sql); 305 | } 306 | unset($sql_query); 307 | } 308 | 309 | if (file_exists($install_path . 'schema_data.sql')) 310 | { 311 | $sql_query = file_get_contents($install_path . 'schema_data.sql'); 312 | 313 | switch ($install_dbms) 314 | { 315 | case 'mssql': 316 | case 'mssql_odbc': 317 | $sql_query = preg_replace('#\# MSSQL IDENTITY (phpbb_[a-z_]+) (ON|OFF) \##s', 'SET IDENTITY_INSERT \1 \2;', $sql_query); 318 | break; 319 | 320 | case 'postgres': 321 | $sql_query = preg_replace('#\# POSTGRES (BEGIN|COMMIT) \##s', '\1; ', $sql_query); 322 | break; 323 | } 324 | 325 | $sql_query = preg_replace('# phpbb_([^\s]*) #i', ' ' . $table_prefix . '\1 ', $sql_query); 326 | $sql_query = preg_replace_callback('#\{L_([A-Z0-9\-_]*)\}#s', 'adjust_language_keys_callback', $sql_query); 327 | 328 | remove_remarks($sql_query); 329 | 330 | $sql_query = qi_split_sql_file($sql_query, ';'); 331 | 332 | foreach ($sql_query as $sql) 333 | { 334 | $db->sql_query($sql); 335 | } 336 | unset($sql_query); 337 | } 338 | } 339 | 340 | function qi_split_sql_file($sql, $delimiter) 341 | { 342 | if (qi::phpbb_branch('3.2')) 343 | { 344 | global $phpbb_root_path; 345 | $database = new \phpbb\install\helper\database(new \phpbb\filesystem\filesystem(), $phpbb_root_path); 346 | return call_user_func(array($database, 'split_sql_file'), $sql, $delimiter); 347 | } 348 | else 349 | { 350 | return call_user_func('split_sql_file', $sql, $delimiter); 351 | } 352 | } 353 | 354 | function qi_remove_comments($input) 355 | { 356 | if (qi::phpbb_branch('3.2')) 357 | { 358 | global $phpbb_root_path; 359 | $database = new \phpbb\install\helper\database(new \phpbb\filesystem\filesystem(), $phpbb_root_path); 360 | return call_user_func(array($database, 'remove_comments'), $input); 361 | } 362 | else 363 | { 364 | return call_user_func('phpbb_remove_comments', $input); 365 | } 366 | } 367 | 368 | function qi_adjust_language_keys_callback($matches) 369 | { 370 | if (!empty($matches[1])) 371 | { 372 | global $lang, $db; 373 | 374 | return (!empty($lang[$matches[1]])) ? $db->sql_escape($lang[$matches[1]]) : $db->sql_escape($matches[1]); 375 | } 376 | } 377 | 378 | -------------------------------------------------------------------------------- /includes/qi_version_helper.php: -------------------------------------------------------------------------------- 1 | file_downloader = new qi_file_downloader(); 60 | } 61 | 62 | /** 63 | * Set location to the file 64 | * 65 | * @param string $host Host (e.g. version.phpbb.com) 66 | * @param string $path Path to file (e.g. /phpbb) 67 | * @param string $file File name (Default: versions.json) 68 | * @param bool $use_ssl Use SSL or not (Default: false) 69 | * @return qi_version_helper 70 | */ 71 | public function set_file_location($host, $path, $file, $use_ssl = false) 72 | { 73 | $this->host = $host; 74 | $this->path = $path; 75 | $this->file = $file; 76 | $this->use_ssl = $use_ssl; 77 | 78 | return $this; 79 | } 80 | 81 | /** 82 | * Set current version 83 | * 84 | * @param string $version The current version 85 | * @return qi_version_helper 86 | */ 87 | public function set_current_version($version) 88 | { 89 | $this->current_version = $version; 90 | 91 | return $this; 92 | } 93 | 94 | /** 95 | * Over-ride the stability to force check to include unstable versions 96 | * 97 | * @param null|string Null to not force stability, 'unstable' or 'stable' to 98 | * force the corresponding stability 99 | * @return qi_version_helper 100 | */ 101 | public function force_stability($stability) 102 | { 103 | $this->force_stability = $stability; 104 | 105 | return $this; 106 | } 107 | 108 | /** 109 | * Wrapper for version_compare() that allows using uppercase A and B 110 | * for alpha and beta releases. 111 | * 112 | * See http://www.php.net/manual/en/function.version-compare.php 113 | * 114 | * @param string $version1 First version number 115 | * @param string $version2 Second version number 116 | * @param string $operator Comparison operator (optional) 117 | * 118 | * @return bool|int Boolean (true, false) if comparison operator is specified. 119 | * Integer (-1, 0, 1) otherwise. 120 | */ 121 | public function compare($version1, $version2, $operator = null) 122 | { 123 | return qi::phpbb_version_compare($version1, $version2, $operator); 124 | } 125 | 126 | /** 127 | * Check whether or not a version is "stable" 128 | * 129 | * Stable means only numbers OR a pl release 130 | * 131 | * @param string $version 132 | * @return bool Bool true or false 133 | */ 134 | public function is_stable($version) 135 | { 136 | $matches = false; 137 | preg_match('/^[\d.]+/', $version, $matches); 138 | 139 | if (empty($matches[0])) 140 | { 141 | return false; 142 | } 143 | 144 | return $this->compare($version, $matches[0], '>='); 145 | } 146 | 147 | /** 148 | * Get latest version update data array 149 | * 150 | * @return array Array of version data, or empty array if no update is available or found. 151 | */ 152 | public function get_update() 153 | { 154 | try 155 | { 156 | $updates = $this->get_suggested_updates(); 157 | return array_pop($updates); 158 | } 159 | catch (RuntimeException $e) 160 | { 161 | return array(); 162 | } 163 | } 164 | 165 | /** 166 | * Obtains the latest version information 167 | * 168 | * @param bool $force_update Ignores cached data. Defaults to false. 169 | * @param bool $force_cache Force the use of the cache. Override $force_update. 170 | * @return array 171 | * @throws RuntimeException 172 | */ 173 | public function get_suggested_updates($force_update = false, $force_cache = false) 174 | { 175 | $versions = $this->get_versions_matching_stability($force_update, $force_cache); 176 | 177 | $self = $this; 178 | $current_version = $this->current_version; 179 | 180 | // Filter out any versions less than or equal to the current version 181 | return array_filter($versions, function($data) use ($self, $current_version) { 182 | return $self->compare($data['current'], $current_version, '>'); 183 | }); 184 | } 185 | 186 | /** 187 | * Obtains the latest version information matching the stability of the current install 188 | * 189 | * @param bool $force_update Ignores cached data. Defaults to false. 190 | * @param bool $force_cache Force the use of the cache. Override $force_update. 191 | * @return array Version info 192 | * @throws RuntimeException 193 | */ 194 | public function get_versions_matching_stability($force_update = false, $force_cache = false) 195 | { 196 | $info = $this->get_versions($force_update, $force_cache); 197 | 198 | if ($this->force_stability !== null) 199 | { 200 | return ($this->force_stability === 'unstable') ? $info['unstable'] : $info['stable']; 201 | } 202 | 203 | return $this->is_stable($this->current_version) ? $info['stable'] : $info['unstable']; 204 | } 205 | 206 | /** 207 | * Obtains the latest version information 208 | * 209 | * @param bool $force_update Ignores cached data. Defaults to false. 210 | * @param bool $force_cache Force the use of the cache. Override $force_update. 211 | * @return array Version info, includes stable and unstable data 212 | * @throws RuntimeException 213 | */ 214 | public function get_versions($force_update = false, $force_cache = false) 215 | { 216 | $cache_file = 'versioncheck' . $this->path; 217 | 218 | $info = $this->cache_get($cache_file); 219 | 220 | if ($info === false && $force_cache) 221 | { 222 | throw new RuntimeException('VERSIONCHECK_FAIL'); 223 | } 224 | if ($info === false || $force_update) 225 | { 226 | $info = $this->file_downloader->get($this->host, $this->path, $this->file, $this->use_ssl ? 443 : 80); 227 | $error_string = $this->file_downloader->get_error_string(); 228 | 229 | if (!empty($error_string)) 230 | { 231 | throw new RuntimeException($error_string); 232 | } 233 | 234 | $info = json_decode($info, true); 235 | 236 | // Sanitize any data we retrieve from a server 237 | if (!empty($info)) 238 | { 239 | $json_sanitizer = function (&$value, $key) { 240 | legacy_set_var($value, $value, gettype($value)); 241 | }; 242 | array_walk_recursive($info, $json_sanitizer); 243 | } 244 | 245 | if (empty($info['stable']) && empty($info['unstable'])) 246 | { 247 | throw new RuntimeException('VERSIONCHECK_FAIL'); 248 | } 249 | 250 | $info['stable'] = empty($info['stable']) ? array() : $info['stable']; 251 | $info['unstable'] = empty($info['unstable']) ? $info['stable'] : $info['unstable']; 252 | 253 | $this->cache_put($cache_file, $info); 254 | } 255 | 256 | return $info; 257 | } 258 | 259 | /** 260 | * Put data in the cache 261 | * 262 | * @param string $handle Name of the cache file 263 | * @param mixed $data The data to store 264 | */ 265 | protected function cache_put($handle, $data) 266 | { 267 | $filename = $this->get_cache_path($handle); 268 | 269 | if ($fp = @fopen($filename, 'wb')) 270 | { 271 | @flock($fp, LOCK_EX); 272 | @fwrite ($fp, json_encode($data)); 273 | @flock($fp, LOCK_UN); 274 | @fclose($fp); 275 | 276 | chmod($filename, 0777); 277 | } 278 | } 279 | 280 | /** 281 | * Get data from the cache 282 | * 283 | * @param string $handle Name of the cache file 284 | * @return bool|mixed The cached data if it exists, false otherwise 285 | */ 286 | protected function cache_get($handle) 287 | { 288 | $filename = $this->get_cache_path($handle); 289 | 290 | if (is_file($filename) && (filemtime($filename) > strtotime('24 hours ago'))) 291 | { 292 | if (!($file_contents = file_get_contents($filename))) 293 | { 294 | return false; 295 | } 296 | 297 | if (($data = json_decode($file_contents, true)) === null) 298 | { 299 | return false; 300 | } 301 | 302 | return $data; 303 | } 304 | 305 | return false; 306 | } 307 | 308 | /** 309 | * Get the pathname for a cache file 310 | * 311 | * @param string $handle Name of the cache file 312 | * @return string The path and name of the cache file 313 | */ 314 | protected function get_cache_path($handle) 315 | { 316 | global $settings; 317 | 318 | return $settings->get_cache_dir() . 'data_' . str_replace('/', '_', $handle) . '.json'; 319 | } 320 | } 321 | 322 | class qi_file_downloader 323 | { 324 | /** @var string Error string */ 325 | protected $error_string = ''; 326 | 327 | /** @var int Error number */ 328 | protected $error_number = 0; 329 | 330 | /** 331 | * Retrieve contents from remotely stored file 332 | * 333 | * @param string $host File host 334 | * @param string $directory Directory file is in 335 | * @param string $filename Filename of file to retrieve 336 | * @param int $port Port to connect to; default: 80 337 | * @param int $timeout Connection timeout in seconds; default: 6 338 | * 339 | * @return false|string File data as string if file can be read and there is no 340 | * timeout, false if there were errors or the connection timed out 341 | * 342 | * @throws RuntimeException() If data can't be retrieved and no error 343 | * message is returned 344 | */ 345 | public function get($host, $directory, $filename, $port = 80, $timeout = 6) 346 | { 347 | // Set default values for error variables 348 | $this->error_number = 0; 349 | $this->error_string = ''; 350 | 351 | if ($socket = @fsockopen(($port == 443 ? 'tls://' : '') . $host, $port, $this->error_number, $this->error_string, $timeout)) 352 | { 353 | @fwrite($socket, "GET $directory/$filename HTTP/1.0\r\n"); 354 | @fwrite($socket, "HOST: $host\r\n"); 355 | @fwrite($socket, "Connection: close\r\n\r\n"); 356 | 357 | $timer_stop = time() + $timeout; 358 | stream_set_timeout($socket, $timeout); 359 | 360 | $file_info = ''; 361 | $get_info = false; 362 | 363 | while (!@feof($socket)) 364 | { 365 | if ($get_info) 366 | { 367 | $file_info .= @fread($socket, 1024); 368 | } 369 | else 370 | { 371 | $line = @fgets($socket, 1024); 372 | if ($line === "\r\n") 373 | { 374 | $get_info = true; 375 | } 376 | else if (stripos($line, '404 not found') !== false) 377 | { 378 | throw new RuntimeException('FILE_NOT_FOUND'); 379 | } 380 | } 381 | 382 | $stream_meta_data = stream_get_meta_data($socket); 383 | 384 | if (!empty($stream_meta_data['timed_out']) || time() >= $timer_stop) 385 | { 386 | throw new RuntimeException('FSOCK_TIMEOUT'); 387 | } 388 | } 389 | @fclose($socket); 390 | } 391 | else 392 | { 393 | if ($this->error_string) 394 | { 395 | $this->error_string = utf8_convert_message($this->error_string); 396 | return false; 397 | } 398 | 399 | throw new RuntimeException('FSOCK_DISABLED'); 400 | } 401 | 402 | return $file_info; 403 | } 404 | 405 | /** 406 | * Get error string 407 | * 408 | * @return string Error string 409 | */ 410 | public function get_error_string() 411 | { 412 | return $this->error_string; 413 | } 414 | 415 | /** 416 | * Get error number 417 | * 418 | * @return int Error number 419 | */ 420 | public function get_error_number() 421 | { 422 | return $this->error_number; 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /style/assets/img/logo_small_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /style/assets/img/logo_medium_cosmos.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "b7944817b3b63e8a24c926ed77ceba01", 8 | "packages": [ 9 | { 10 | "name": "erusev/parsedown", 11 | "version": "1.7.4", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/erusev/parsedown.git", 15 | "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", 20 | "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-mbstring": "*", 25 | "php": ">=5.3.0" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "^4.8.35" 29 | }, 30 | "type": "library", 31 | "autoload": { 32 | "psr-0": { 33 | "Parsedown": "" 34 | } 35 | }, 36 | "notification-url": "https://packagist.org/downloads/", 37 | "license": [ 38 | "MIT" 39 | ], 40 | "authors": [ 41 | { 42 | "name": "Emanuil Rusev", 43 | "email": "hello@erusev.com", 44 | "homepage": "http://erusev.com" 45 | } 46 | ], 47 | "description": "Parser for Markdown.", 48 | "homepage": "http://parsedown.org", 49 | "keywords": [ 50 | "markdown", 51 | "parser" 52 | ], 53 | "support": { 54 | "issues": "https://github.com/erusev/parsedown/issues", 55 | "source": "https://github.com/erusev/parsedown/tree/1.7.x" 56 | }, 57 | "time": "2019-12-30T22:54:17+00:00" 58 | }, 59 | { 60 | "name": "symfony/polyfill-ctype", 61 | "version": "v1.19.0", 62 | "source": { 63 | "type": "git", 64 | "url": "https://github.com/symfony/polyfill-ctype.git", 65 | "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b" 66 | }, 67 | "dist": { 68 | "type": "zip", 69 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/aed596913b70fae57be53d86faa2e9ef85a2297b", 70 | "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b", 71 | "shasum": "" 72 | }, 73 | "require": { 74 | "php": ">=5.3.3" 75 | }, 76 | "suggest": { 77 | "ext-ctype": "For best performance" 78 | }, 79 | "type": "library", 80 | "extra": { 81 | "thanks": { 82 | "url": "https://github.com/symfony/polyfill", 83 | "name": "symfony/polyfill" 84 | }, 85 | "branch-alias": { 86 | "dev-main": "1.19-dev" 87 | } 88 | }, 89 | "autoload": { 90 | "files": [ 91 | "bootstrap.php" 92 | ], 93 | "psr-4": { 94 | "Symfony\\Polyfill\\Ctype\\": "" 95 | } 96 | }, 97 | "notification-url": "https://packagist.org/downloads/", 98 | "license": [ 99 | "MIT" 100 | ], 101 | "authors": [ 102 | { 103 | "name": "Gert de Pagter", 104 | "email": "BackEndTea@gmail.com" 105 | }, 106 | { 107 | "name": "Symfony Community", 108 | "homepage": "https://symfony.com/contributors" 109 | } 110 | ], 111 | "description": "Symfony polyfill for ctype functions", 112 | "homepage": "https://symfony.com", 113 | "keywords": [ 114 | "compatibility", 115 | "ctype", 116 | "polyfill", 117 | "portable" 118 | ], 119 | "support": { 120 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.19.0" 121 | }, 122 | "funding": [ 123 | { 124 | "url": "https://symfony.com/sponsor", 125 | "type": "custom" 126 | }, 127 | { 128 | "url": "https://github.com/fabpot", 129 | "type": "github" 130 | }, 131 | { 132 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 133 | "type": "tidelift" 134 | } 135 | ], 136 | "time": "2020-10-23T09:01:57+00:00" 137 | }, 138 | { 139 | "name": "twig/twig", 140 | "version": "v1.42.2", 141 | "source": { 142 | "type": "git", 143 | "url": "https://github.com/twigphp/Twig.git", 144 | "reference": "21707d6ebd05476854805e4f91b836531941bcd4" 145 | }, 146 | "dist": { 147 | "type": "zip", 148 | "url": "https://api.github.com/repos/twigphp/Twig/zipball/21707d6ebd05476854805e4f91b836531941bcd4", 149 | "reference": "21707d6ebd05476854805e4f91b836531941bcd4", 150 | "shasum": "" 151 | }, 152 | "require": { 153 | "php": ">=5.4.0", 154 | "symfony/polyfill-ctype": "^1.8" 155 | }, 156 | "require-dev": { 157 | "psr/container": "^1.0", 158 | "symfony/debug": "^2.7", 159 | "symfony/phpunit-bridge": "^3.4.19|^4.1.8|^5.0" 160 | }, 161 | "type": "library", 162 | "extra": { 163 | "branch-alias": { 164 | "dev-master": "1.42-dev" 165 | } 166 | }, 167 | "autoload": { 168 | "psr-0": { 169 | "Twig_": "lib/" 170 | }, 171 | "psr-4": { 172 | "Twig\\": "src/" 173 | } 174 | }, 175 | "notification-url": "https://packagist.org/downloads/", 176 | "license": [ 177 | "BSD-3-Clause" 178 | ], 179 | "authors": [ 180 | { 181 | "name": "Fabien Potencier", 182 | "email": "fabien@symfony.com", 183 | "homepage": "http://fabien.potencier.org", 184 | "role": "Lead Developer" 185 | }, 186 | { 187 | "name": "Armin Ronacher", 188 | "email": "armin.ronacher@active-4.com", 189 | "role": "Project Founder" 190 | }, 191 | { 192 | "name": "Twig Team", 193 | "homepage": "https://twig.symfony.com/contributors", 194 | "role": "Contributors" 195 | } 196 | ], 197 | "description": "Twig, the flexible, fast, and secure template language for PHP", 198 | "homepage": "https://twig.symfony.com", 199 | "keywords": [ 200 | "templating" 201 | ], 202 | "support": { 203 | "issues": "https://github.com/twigphp/Twig/issues", 204 | "source": "https://github.com/twigphp/Twig/tree/1.x" 205 | }, 206 | "time": "2019-06-18T15:35:16+00:00" 207 | } 208 | ], 209 | "packages-dev": [ 210 | { 211 | "name": "phing/phing", 212 | "version": "2.17.4", 213 | "source": { 214 | "type": "git", 215 | "url": "https://github.com/phingofficial/phing.git", 216 | "reference": "9f3bc8c72e65452686dcf64497e02a082f138908" 217 | }, 218 | "dist": { 219 | "type": "zip", 220 | "url": "https://api.github.com/repos/phingofficial/phing/zipball/9f3bc8c72e65452686dcf64497e02a082f138908", 221 | "reference": "9f3bc8c72e65452686dcf64497e02a082f138908", 222 | "shasum": "" 223 | }, 224 | "require": { 225 | "php": ">=5.2.0" 226 | }, 227 | "require-dev": { 228 | "ext-pdo_sqlite": "*", 229 | "mikey179/vfsstream": "^1.6", 230 | "pdepend/pdepend": "2.x", 231 | "pear/archive_tar": "1.4.x", 232 | "pear/http_request2": "dev-trunk", 233 | "pear/net_growl": "dev-trunk", 234 | "pear/pear-core-minimal": "1.10.1", 235 | "pear/versioncontrol_git": "@dev", 236 | "pear/versioncontrol_svn": "~0.5", 237 | "phpdocumentor/phpdocumentor": "2.x", 238 | "phploc/phploc": "~2.0.6", 239 | "phpmd/phpmd": "~2.2", 240 | "phpunit/phpunit": ">=3.7", 241 | "sebastian/git": "~1.0", 242 | "sebastian/phpcpd": "2.x", 243 | "siad007/versioncontrol_hg": "^1.0", 244 | "simpletest/simpletest": "^1.1", 245 | "squizlabs/php_codesniffer": "~2.2", 246 | "symfony/yaml": "^2.8 || ^3.1 || ^4.0" 247 | }, 248 | "suggest": { 249 | "pdepend/pdepend": "PHP version of JDepend", 250 | "pear/archive_tar": "Tar file management class", 251 | "pear/versioncontrol_git": "A library that provides OO interface to handle Git repository", 252 | "pear/versioncontrol_svn": "A simple OO-style interface for Subversion, the free/open-source version control system", 253 | "phpdocumentor/phpdocumentor": "Documentation Generator for PHP", 254 | "phploc/phploc": "A tool for quickly measuring the size of a PHP project", 255 | "phpmd/phpmd": "PHP version of PMD tool", 256 | "phpunit/php-code-coverage": "Library that provides collection, processing, and rendering functionality for PHP code coverage information", 257 | "phpunit/phpunit": "The PHP Unit Testing Framework", 258 | "sebastian/phpcpd": "Copy/Paste Detector (CPD) for PHP code", 259 | "siad007/versioncontrol_hg": "A library for interfacing with Mercurial repositories.", 260 | "tedivm/jshrink": "Javascript Minifier built in PHP" 261 | }, 262 | "bin": [ 263 | "bin/phing" 264 | ], 265 | "type": "library", 266 | "extra": { 267 | "branch-alias": { 268 | "dev-master": "2.16.x-dev" 269 | } 270 | }, 271 | "autoload": { 272 | "classmap": [ 273 | "classes/phing/" 274 | ] 275 | }, 276 | "notification-url": "https://packagist.org/downloads/", 277 | "include-path": [ 278 | "classes" 279 | ], 280 | "license": [ 281 | "LGPL-3.0-only" 282 | ], 283 | "authors": [ 284 | { 285 | "name": "Michiel Rook", 286 | "email": "mrook@php.net" 287 | }, 288 | { 289 | "name": "Phing Community", 290 | "homepage": "https://www.phing.info/trac/wiki/Development/Contributors" 291 | } 292 | ], 293 | "description": "PHing Is Not GNU make; it's a PHP project build system or build tool based on Apache Ant.", 294 | "homepage": "https://www.phing.info/", 295 | "keywords": [ 296 | "build", 297 | "phing", 298 | "task", 299 | "tool" 300 | ], 301 | "support": { 302 | "irc": "irc://irc.freenode.net/phing", 303 | "issues": "https://www.phing.info/trac/report", 304 | "source": "https://github.com/phingofficial/phing/tree/2.17.4" 305 | }, 306 | "funding": [ 307 | { 308 | "url": "https://github.com/mrook", 309 | "type": "github" 310 | }, 311 | { 312 | "url": "https://github.com/siad007", 313 | "type": "github" 314 | }, 315 | { 316 | "url": "https://www.patreon.com/michielrook", 317 | "type": "patreon" 318 | } 319 | ], 320 | "time": "2022-07-08T09:07:07+00:00" 321 | } 322 | ], 323 | "aliases": [], 324 | "minimum-stability": "stable", 325 | "stability-flags": [], 326 | "prefer-stable": false, 327 | "prefer-lowest": false, 328 | "platform": { 329 | "php": ">=5.4", 330 | "ext-json": "*" 331 | }, 332 | "platform-dev": [], 333 | "platform-overrides": { 334 | "php": "5.4.7" 335 | }, 336 | "plugin-api-version": "2.2.0" 337 | } 338 | -------------------------------------------------------------------------------- /includes/settings.php: -------------------------------------------------------------------------------- 1 | qi_path = $quickinstall_path; 38 | } 39 | 40 | /** 41 | * Import a settings profile into the settings property. 42 | * 43 | * @param string $profile The name of a settings profile 44 | * @param bool $cookie Are we loading a profile from a cookie? 45 | * @return bool Returns true on successful import, false on failure. 46 | */ 47 | public function import_profile($profile = '', $cookie = false) 48 | { 49 | // Load requested profile into settings property 50 | if ($profile !== '') 51 | { 52 | $settings = $this->load_profile("{$this->qi_path}settings/$profile.json"); 53 | 54 | if (empty($settings)) 55 | { 56 | return false; 57 | } 58 | 59 | $this->settings = $settings; 60 | $this->profile = $profile; 61 | if (!$cookie) 62 | { 63 | $this->set_profile_cookie($profile); 64 | } 65 | 66 | return true; 67 | } 68 | 69 | // No profile, let's try to load recursively from a cookie 70 | if (!$cookie && ($profile = $this->get_profile_cookie()) !== '' && $this->import_profile($profile, true)) 71 | { 72 | return true; 73 | } 74 | 75 | // No cookie, lets try to load recursively the first of any available profiles 76 | if ((($profile = $this->find_first_profile()) !== '') && $this->import_profile($profile)) 77 | { 78 | return true; 79 | } 80 | 81 | // Still no profile found. Maybe we have old .cfg files to update? 82 | if ($this->convert_cfg_to_json() && $this->import_profile()) 83 | { 84 | return true; 85 | } 86 | 87 | // If we reach this point there are no profiles. 88 | // Most likely a new user so load the default settings and go to install. 89 | $this->settings = $this->load_profile("{$this->qi_path}includes/default_settings.json"); 90 | $this->install = qi_request_var('mode', '') !== 'update_settings'; 91 | 92 | return true; 93 | } 94 | 95 | /** 96 | * Load a profile settings document 97 | * 98 | * @param string $profile Name of a settings profile JSON document 99 | * @return array|false Return array of settings, or false if something went wrong 100 | */ 101 | protected function load_profile($profile) 102 | { 103 | return json_decode(@file_get_contents($profile), true); 104 | } 105 | 106 | /** 107 | * Get the name of the first profile found in the settings directory 108 | * 109 | * @return string Name of found settings profile, or empty 110 | */ 111 | protected function find_first_profile() 112 | { 113 | return (($profiles = $this->get_profiles()) !== false) ? array_keys($profiles)[0] : ''; 114 | } 115 | 116 | /** 117 | * Validates settings. 118 | * If validation fails, the errors are available in $errors property. 119 | * 120 | * @return bool True if valid, false if any validation failed. 121 | */ 122 | public function validate() 123 | { 124 | if (empty($this->settings)) 125 | { 126 | $this->errors[] = 'CONFIG_EMPTY'; 127 | return false; 128 | } 129 | 130 | $this->settings = array_map('htmlspecialchars_decode', $this->settings); 131 | 132 | $validation_errors = []; 133 | 134 | // Let's check simple required string settings... 135 | foreach (['cache_dir', 'boards_dir', 'boards_url', 'dbms', 'dbhost', 'table_prefix', 'qi_lang', 'qi_tz', 'db_prefix', 'admin_email', 'email_domain', 'site_name', 'server_name', 'server_port', 'board_email', 'default_lang'] as $setting) 136 | { 137 | if ($this->settings[$setting] === '') 138 | { 139 | $validation_errors[] = ['IS_REQUIRED', strtoupper($setting)]; 140 | } 141 | } 142 | 143 | // Validate database password setting 144 | if ($this->settings['dbpasswd'] !== '' && !empty($this->settings['no_dbpasswd'])) 145 | { 146 | $validation_errors[] = 'NO_DBPASSWD_ERR'; 147 | } 148 | 149 | // Validate database prefix 150 | if ($this->settings['db_prefix'] !== validate_dbname($this->settings['db_prefix'], true)) 151 | { 152 | $validation_errors[] = ['IS_NOT_VALID', 'DB_PREFIX']; 153 | } 154 | 155 | // Validate cache directory 156 | qi_file::append_slash($this->settings['cache_dir']); 157 | if (!file_exists($this->get_cache_dir()) || !is_writable($this->get_cache_dir())) 158 | { 159 | $validation_errors[] = ['CACHE_DIR_MISSING', $this->get_cache_dir()]; 160 | } 161 | 162 | // Validate boards directory 163 | qi_file::append_slash($this->settings['boards_dir']); 164 | if (!file_exists($this->get_boards_dir()) || !is_writable($this->get_boards_dir())) 165 | { 166 | $validation_errors[] = ['BOARDS_DIR_MISSING', $this->get_boards_dir()]; 167 | } 168 | 169 | // Validate alt environment board's compatibility with current PHP environment 170 | if ($this->settings['alt_env']) 171 | { 172 | $phpbb_version = get_phpbb_version($this->qi_path . 'sources/phpBB3_alt/' . $this->settings['alt_env']); 173 | if (qi::php_phpbb_incompatible($phpbb_version)) 174 | { 175 | $validation_errors[] = ['PHP_INCOMPATIBLE', $phpbb_version, PHP_VERSION]; 176 | } 177 | } 178 | 179 | // Adjust boards URL path 180 | qi_file::append_slash($this->settings['boards_url']); 181 | 182 | // SQLite needs a writable and existing directory 183 | if (in_array($this->settings['dbms'], ['sqlite', 'sqlite3'])) 184 | { 185 | qi_file::append_slash($this->settings['dbhost']); 186 | if (!file_exists($this->settings['dbhost']) || !is_writable($this->settings['dbhost']) || !is_dir($this->settings['dbhost'])) 187 | { 188 | $validation_errors[] = 'SQLITE_PATH_MISSING'; 189 | } 190 | } 191 | 192 | $this->errors = array_merge($this->errors, $validation_errors); 193 | 194 | return empty($validation_errors); 195 | } 196 | 197 | /** 198 | * Save a profile 199 | * 200 | * @param string $profile Name of profile 201 | * @param array $settings The settings data array 202 | * @return bool True or false if profile was saved 203 | */ 204 | public function save_profile($profile = '', $settings = []) 205 | { 206 | $profile = $profile !== '' ? $profile : $this->profile; 207 | $settings = $settings !== [] ? $settings : $this->settings; 208 | 209 | $profile_file = "{$this->qi_path}settings/$profile.json"; 210 | 211 | $saved = qi_file::make_file($profile_file, $this->encode_settings($settings)); 212 | 213 | // Make install false if settings have been successfully saved. 214 | if ($saved !== false) 215 | { 216 | $this->install = false; 217 | } 218 | 219 | return $saved; 220 | } 221 | 222 | /** 223 | * Delete a profile 224 | * 225 | * @param string $profile Name of profile 226 | * @param string $ext The file extension (default is json) 227 | */ 228 | public function delete_profile($profile, $ext = 'json') 229 | { 230 | qi_file::delete_file("{$this->qi_path}settings/$profile.$ext"); 231 | } 232 | 233 | /** 234 | * Get the name of the current profile 235 | * 236 | * @return string 237 | */ 238 | public function get_profile() 239 | { 240 | return $this->profile; 241 | } 242 | 243 | /** 244 | * Scans the settings directory and returns an array all setting profiles, 245 | * typically for use in select menus. 246 | * [ 247 | * 'profileName' => true/false is the current/selected profile 248 | * ] 249 | * 250 | * @return array|false Array of profiles or false if nothing found. 251 | */ 252 | public function get_profiles() 253 | { 254 | if (($files = $this->find_profiles()) !== false) 255 | { 256 | $profiles = []; 257 | 258 | foreach ($files as $file) 259 | { 260 | $cfg_name = pathinfo($file, PATHINFO_FILENAME); 261 | $profiles[$cfg_name] = $cfg_name === $this->profile; 262 | } 263 | 264 | return $profiles; 265 | } 266 | 267 | return false; 268 | } 269 | 270 | /** 271 | * Scan settings directory for files 272 | * 273 | * @param string $type The file type by extension, default is json. 274 | * @return array|false Array of files found or false if nothing found. 275 | */ 276 | protected function find_profiles($type = 'json') 277 | { 278 | if (($files = scandir("{$this->qi_path}settings")) !== false) 279 | { 280 | foreach ($files as $key => $file) 281 | { 282 | if ($file[0] === '.' || pathinfo($file, PATHINFO_EXTENSION) !== $type || !is_readable("{$this->qi_path}settings/$file")) 283 | { 284 | unset($files[$key]); 285 | } 286 | } 287 | 288 | sort($files, SORT_NATURAL | SORT_FLAG_CASE); 289 | } 290 | 291 | return !empty($files) ? $files : false; 292 | } 293 | 294 | /** 295 | * Set a cookie storing a profile name 296 | * 297 | * @param string $value The name of a profile 298 | */ 299 | public function set_profile_cookie($value) 300 | { 301 | qi::set_cookie('qi_profile', $value); 302 | } 303 | 304 | /** 305 | * Get a cookie holding a profile name 306 | * 307 | * @return string The name of a profile 308 | */ 309 | public function get_profile_cookie() 310 | { 311 | return isset($_COOKIE['qi_profile']) ? $_COOKIE['qi_profile'] : ''; 312 | } 313 | 314 | /** 315 | * Convert older .cfg profiles to .json 316 | * 317 | * @return bool True if profiles were converted, false if nothing happened. 318 | */ 319 | public function convert_cfg_to_json() 320 | { 321 | if (($cfg_files = $this->find_profiles('cfg')) === false) 322 | { 323 | return false; 324 | } 325 | 326 | foreach ($cfg_files as $file) 327 | { 328 | $data = file("{$this->qi_path}settings/$file", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 329 | $settings = []; 330 | 331 | foreach ($data as $row) 332 | { 333 | if (($row = trim($row)) === '') 334 | { 335 | continue; 336 | } 337 | 338 | $parts = explode('=', $row, 2); 339 | 340 | if (($key = trim($parts[0])) === '') 341 | { 342 | continue; 343 | } 344 | 345 | if ($key === 'other_config') 346 | { 347 | $parts[1] = implode("\n", unserialize($parts[1])); 348 | } 349 | 350 | $settings[$key] = isset($parts[1]) ? trim($parts[1]) : ''; 351 | } 352 | 353 | $profile = pathinfo($file, PATHINFO_FILENAME); 354 | if ($this->save_profile($profile, $settings)) 355 | { 356 | $this->delete_profile($profile, 'cfg'); 357 | } 358 | } 359 | 360 | return true; 361 | } 362 | 363 | /** 364 | * Get a config setting or request a post/get var 365 | * 366 | * @param string $name config/var name. 367 | * @param mixed $default Default value and type cast, 0 or '' 368 | * @param bool $multibyte Allow multibyte strings? 369 | * @return mixed The config value requested, or the default value if not found 370 | */ 371 | public function get_config($name, $default = '', $multibyte = false) 372 | { 373 | // First check if we have a post/get var 374 | if (is_string($default)) 375 | { 376 | $request = !empty($_GET[$name]) || !empty($_POST[$name]); 377 | } 378 | else 379 | { 380 | $request = isset($_GET[$name]) || isset($_POST[$name]); 381 | } 382 | 383 | if ($request) 384 | { 385 | return qi_request_var($name, $default, $multibyte); 386 | } 387 | 388 | // Nothing from post/get. Do we have a config setting? 389 | if (!empty($this->settings[$name])) 390 | { 391 | $type = gettype($default); 392 | if (in_array($type, ['string', 'integer', 'double', 'boolean'])) 393 | { 394 | $setting = $this->settings[$name]; 395 | settype($setting, $type); 396 | return $setting; 397 | } 398 | } 399 | 400 | return $default; 401 | } 402 | 403 | /** 404 | * Update the settings property with new values 405 | * 406 | * @param array $data An array of settings 407 | * @return string The profile name (new profile if one was saved, otherwise current profile) 408 | */ 409 | public function set_settings($data) 410 | { 411 | $this->settings = $data; 412 | 413 | $profile = qi_request_var('save_profile', ''); 414 | 415 | if ($profile !== '') 416 | { 417 | // Replace/remove illegal characters 418 | $this->profile = preg_replace(['/\s+/', '/[^A-Za-z0-9_.\-]*/'], ['_', ''], $profile); 419 | } 420 | 421 | return $this->profile; 422 | } 423 | 424 | /** 425 | * Get the board's directory path 426 | * 427 | * @return string Board directory path from settings, otherwise QI's default 428 | */ 429 | public function get_boards_dir() 430 | { 431 | return empty($this->settings['boards_dir']) ? "{$this->qi_path}boards/" : $this->settings['boards_dir']; 432 | } 433 | 434 | /** 435 | * Get the boards URL path 436 | * 437 | * @return string Board URL path from settings, otherwise QI's default 438 | */ 439 | public function get_boards_url() 440 | { 441 | return empty($this->settings['boards_url']) ? "{$this->qi_path}boards/" : $this->settings['boards_url']; 442 | } 443 | 444 | /** 445 | * Get the cache directory path 446 | * 447 | * @return string Cache directory path from settings, otherwise QI's default 448 | */ 449 | public function get_cache_dir() 450 | { 451 | return empty($this->settings['cache_dir']) ? "{$this->qi_path}cache/" : $this->settings['cache_dir']; 452 | } 453 | 454 | /** 455 | * Get the server protocol 456 | * 457 | * @return string Server protocol from settings, otherwise QI's default 458 | */ 459 | public function get_server_protocol() 460 | { 461 | // There is no setting for server_protocol ATM, but there might be in the future so let's keep this for now. 462 | return empty($this->settings['server_protocol']) ? 'http://' : $this->settings['server_protocol']; 463 | } 464 | 465 | /** 466 | * Get the database connection settings 467 | * 468 | * @return array Database connection settings 469 | */ 470 | public function get_db_data() 471 | { 472 | // The order in this array is important, don't change it. 473 | // The caller uses list() to set DB vars. list() only works with numerical arrays. 474 | return [ 475 | $this->get_config('dbms'), 476 | $this->get_config('dbhost'), 477 | $this->get_config('dbuser'), 478 | $this->get_config('dbpasswd'), 479 | $this->get_config('dbport'), 480 | $this->get_config('db_prefix'), 481 | ]; 482 | } 483 | 484 | /** 485 | * Get the current settings in JSON format 486 | * 487 | * @return false|string 488 | */ 489 | public function get_config_text() 490 | { 491 | return $this->encode_settings($this->settings); 492 | } 493 | 494 | /** 495 | * Translate errors 496 | * 497 | * @return array An array containing translated errors, or empty for no error. 498 | */ 499 | public function get_errors() 500 | { 501 | $errors = []; 502 | 503 | if (count($this->errors)) 504 | { 505 | foreach ($this->errors as $error) 506 | { 507 | if (is_array($error)) 508 | { 509 | $key = array_shift($error); 510 | $errors[] = vsprintf(qi::lang($key), array_map(static function($i) { 511 | return qi::lang($i); 512 | }, $error)); 513 | } 514 | else 515 | { 516 | $errors[] = qi::lang($error); 517 | } 518 | } 519 | 520 | $this->errors = []; // Empty the errors. 521 | } 522 | 523 | return $errors; 524 | } 525 | 526 | /** 527 | * Convert settings array into pretty JSON 528 | * 529 | * @param array $settings An array of settings data 530 | * @return false|string The JSON data string, or false if there was some error 531 | */ 532 | protected function encode_settings($settings) 533 | { 534 | return json_encode($settings, JSON_PRETTY_PRINT); 535 | } 536 | 537 | /** 538 | * @return bool 539 | */ 540 | public function is_install() 541 | { 542 | return $this->install; 543 | } 544 | } 545 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | --------------------------------------------------------------------------------