Yoshi2889 /
SMF2.1
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * This file contains functions that are specifically done by administrators. |
||
| 5 | * |
||
| 6 | * Simple Machines Forum (SMF) |
||
| 7 | * |
||
| 8 | * @package SMF |
||
| 9 | * @author Simple Machines https://www.simplemachines.org |
||
| 10 | * @copyright 2020 Simple Machines and individual contributors |
||
| 11 | * @license https://www.simplemachines.org/about/smf/license.php BSD |
||
| 12 | * |
||
| 13 | * @version 2.1 RC2 |
||
| 14 | */ |
||
| 15 | |||
| 16 | if (!defined('SMF')) |
||
| 17 | die('No direct access...'); |
||
| 18 | |||
| 19 | /** |
||
| 20 | * Get a list of versions that are currently installed on the server. |
||
| 21 | * |
||
| 22 | * @param array $checkFor An array of what to check versions for - can contain one or more of 'gd', 'imagemagick', 'db_server', 'phpa', 'memcache', 'xcache', 'apc', 'php' or 'server' |
||
| 23 | * @return array An array of versions (keys are same as what was in $checkFor, values are the versions) |
||
| 24 | */ |
||
| 25 | function getServerVersions($checkFor) |
||
| 26 | { |
||
| 27 | global $txt, $db_connection, $_PHPA, $smcFunc, $cache_accelerator, $cache_memcached, $cacheAPI, $modSettings; |
||
| 28 | |||
| 29 | loadLanguage('Admin'); |
||
| 30 | |||
| 31 | $versions = array(); |
||
| 32 | |||
| 33 | // Is GD available? If it is, we should show version information for it too. |
||
| 34 | if (in_array('gd', $checkFor) && function_exists('gd_info')) |
||
| 35 | { |
||
| 36 | $temp = gd_info(); |
||
| 37 | $versions['gd'] = array('title' => $txt['support_versions_gd'], 'version' => $temp['GD Version']); |
||
| 38 | } |
||
| 39 | |||
| 40 | // Why not have a look at ImageMagick? If it's installed, we should show version information for it too. |
||
| 41 | if (in_array('imagemagick', $checkFor) && (class_exists('Imagick') || function_exists('MagickGetVersionString'))) |
||
| 42 | { |
||
| 43 | if (class_exists('Imagick')) |
||
| 44 | { |
||
| 45 | $temp = New Imagick; |
||
| 46 | $temp2 = $temp->getVersion(); |
||
| 47 | $im_version = $temp2['versionString']; |
||
| 48 | $extension_version = 'Imagick ' . phpversion('Imagick'); |
||
| 49 | } |
||
| 50 | else |
||
| 51 | { |
||
| 52 | $im_version = MagickGetVersionString(); |
||
| 53 | $extension_version = 'MagickWand ' . phpversion('MagickWand'); |
||
| 54 | } |
||
| 55 | |||
| 56 | // We already know it's ImageMagick and the website isn't needed... |
||
| 57 | $im_version = str_replace(array('ImageMagick ', ' https://www.imagemagick.org'), '', $im_version); |
||
| 58 | $versions['imagemagick'] = array('title' => $txt['support_versions_imagemagick'], 'version' => $im_version . ' (' . $extension_version . ')'); |
||
| 59 | } |
||
| 60 | |||
| 61 | // Now lets check for the Database. |
||
| 62 | if (in_array('db_server', $checkFor)) |
||
| 63 | { |
||
| 64 | db_extend(); |
||
| 65 | if (!isset($db_connection) || $db_connection === false) |
||
| 66 | trigger_error('getServerVersions(): you need to be connected to the database in order to get its server version', E_USER_NOTICE); |
||
| 67 | else |
||
| 68 | { |
||
| 69 | $versions['db_engine'] = array('title' => sprintf($txt['support_versions_db_engine'], $smcFunc['db_title']), 'version' => ''); |
||
| 70 | $versions['db_engine']['version'] = $smcFunc['db_get_vendor'](); |
||
| 71 | |||
| 72 | $versions['db_server'] = array('title' => sprintf($txt['support_versions_db'], $smcFunc['db_title']), 'version' => ''); |
||
| 73 | $versions['db_server']['version'] = $smcFunc['db_get_version'](); |
||
| 74 | } |
||
| 75 | } |
||
| 76 | |||
| 77 | // If we're using memcache we need the server info. |
||
| 78 | $memcache_version = '???'; |
||
| 79 | if (!empty($cache_accelerator) && ($cache_accelerator == 'memcached' || $cache_accelerator == 'memcache') && !empty($cache_memcached) && !empty($cacheAPI)) |
||
| 80 | $memcache_version = $cacheAPI->getVersion(); |
||
| 81 | |||
| 82 | // Check to see if we have any accelerators installed... |
||
| 83 | if (in_array('phpa', $checkFor) && isset($_PHPA)) |
||
| 84 | $versions['phpa'] = array('title' => 'ionCube PHP-Accelerator', 'version' => $_PHPA['VERSION']); |
||
| 85 | if (in_array('apc', $checkFor) && extension_loaded('apc')) |
||
| 86 | $versions['apc'] = array('title' => 'Alternative PHP Cache', 'version' => phpversion('apc')); |
||
| 87 | if (in_array('memcache', $checkFor) && function_exists('memcache_set')) |
||
| 88 | $versions['memcache'] = array('title' => 'Memcached', 'version' => $memcache_version); |
||
| 89 | if (in_array('xcache', $checkFor) && function_exists('xcache_set')) |
||
| 90 | $versions['xcache'] = array('title' => 'XCache', 'version' => XCACHE_VERSION); |
||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 91 | |||
| 92 | if (in_array('php', $checkFor)) |
||
| 93 | $versions['php'] = array('title' => 'PHP', 'version' => PHP_VERSION, 'more' => '?action=admin;area=serversettings;sa=phpinfo'); |
||
| 94 | |||
| 95 | if (in_array('server', $checkFor)) |
||
| 96 | $versions['server'] = array('title' => $txt['support_versions_server'], 'version' => $_SERVER['SERVER_SOFTWARE']); |
||
| 97 | |||
| 98 | return $versions; |
||
| 99 | } |
||
| 100 | |||
| 101 | /** |
||
| 102 | * Search through source, theme and language files to determine their version. |
||
| 103 | * Get detailed version information about the physical SMF files on the server. |
||
| 104 | * |
||
| 105 | * - the input parameter allows to set whether to include SSI.php and whether |
||
| 106 | * the results should be sorted. |
||
| 107 | * - returns an array containing information on source files, templates and |
||
| 108 | * language files found in the default theme directory (grouped by language). |
||
| 109 | * |
||
| 110 | * @param array &$versionOptions An array of options. Can contain one or more of 'include_ssi', 'include_subscriptions', 'include_tasks' and 'sort_results' |
||
| 111 | * @return array An array of file version info. |
||
| 112 | */ |
||
| 113 | function getFileVersions(&$versionOptions) |
||
| 114 | { |
||
| 115 | global $boarddir, $sourcedir, $settings, $tasksdir; |
||
| 116 | |||
| 117 | // Default place to find the languages would be the default theme dir. |
||
| 118 | $lang_dir = $settings['default_theme_dir'] . '/languages'; |
||
| 119 | |||
| 120 | $version_info = array( |
||
| 121 | 'file_versions' => array(), |
||
| 122 | 'default_template_versions' => array(), |
||
| 123 | 'template_versions' => array(), |
||
| 124 | 'default_language_versions' => array(), |
||
| 125 | 'tasks_versions' => array(), |
||
| 126 | ); |
||
| 127 | |||
| 128 | // Find the version in SSI.php's file header. |
||
| 129 | if (!empty($versionOptions['include_ssi']) && file_exists($boarddir . '/SSI.php')) |
||
| 130 | { |
||
| 131 | $fp = fopen($boarddir . '/SSI.php', 'rb'); |
||
| 132 | $header = fread($fp, 4096); |
||
| 133 | fclose($fp); |
||
| 134 | |||
| 135 | // The comment looks rougly like... that. |
||
| 136 | if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) |
||
| 137 | $version_info['file_versions']['SSI.php'] = $match[1]; |
||
| 138 | // Not found! This is bad. |
||
| 139 | else |
||
| 140 | $version_info['file_versions']['SSI.php'] = '??'; |
||
| 141 | } |
||
| 142 | |||
| 143 | // Do the paid subscriptions handler? |
||
| 144 | if (!empty($versionOptions['include_subscriptions']) && file_exists($boarddir . '/subscriptions.php')) |
||
| 145 | { |
||
| 146 | $fp = fopen($boarddir . '/subscriptions.php', 'rb'); |
||
| 147 | $header = fread($fp, 4096); |
||
| 148 | fclose($fp); |
||
| 149 | |||
| 150 | // Found it? |
||
| 151 | if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) |
||
| 152 | $version_info['file_versions']['subscriptions.php'] = $match[1]; |
||
| 153 | // If we haven't how do we all get paid? |
||
| 154 | else |
||
| 155 | $version_info['file_versions']['subscriptions.php'] = '??'; |
||
| 156 | } |
||
| 157 | |||
| 158 | // Load all the files in the Sources directory, except this file and the redirect. |
||
| 159 | $sources_dir = dir($sourcedir); |
||
| 160 | while ($entry = $sources_dir->read()) |
||
| 161 | { |
||
| 162 | if (substr($entry, -4) === '.php' && !is_dir($sourcedir . '/' . $entry) && $entry !== 'index.php') |
||
| 163 | { |
||
| 164 | // Read the first 4k from the file.... enough for the header. |
||
| 165 | $fp = fopen($sourcedir . '/' . $entry, 'rb'); |
||
| 166 | $header = fread($fp, 4096); |
||
| 167 | fclose($fp); |
||
| 168 | |||
| 169 | // Look for the version comment in the file header. |
||
| 170 | if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) |
||
| 171 | $version_info['file_versions'][$entry] = $match[1]; |
||
| 172 | // It wasn't found, but the file was... show a '??'. |
||
| 173 | else |
||
| 174 | $version_info['file_versions'][$entry] = '??'; |
||
| 175 | } |
||
| 176 | } |
||
| 177 | $sources_dir->close(); |
||
| 178 | |||
| 179 | // Load all the files in the tasks directory. |
||
| 180 | if (!empty($versionOptions['include_tasks'])) |
||
| 181 | { |
||
| 182 | $tasks_dir = dir($tasksdir); |
||
| 183 | while ($entry = $tasks_dir->read()) |
||
| 184 | { |
||
| 185 | if (substr($entry, -4) === '.php' && !is_dir($tasksdir . '/' . $entry) && $entry !== 'index.php') |
||
| 186 | { |
||
| 187 | // Read the first 4k from the file.... enough for the header. |
||
| 188 | $fp = fopen($tasksdir . '/' . $entry, 'rb'); |
||
| 189 | $header = fread($fp, 4096); |
||
| 190 | fclose($fp); |
||
| 191 | |||
| 192 | // Look for the version comment in the file header. |
||
| 193 | if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) |
||
| 194 | $version_info['tasks_versions'][$entry] = $match[1]; |
||
| 195 | // It wasn't found, but the file was... show a '??'. |
||
| 196 | else |
||
| 197 | $version_info['tasks_versions'][$entry] = '??'; |
||
| 198 | } |
||
| 199 | } |
||
| 200 | $tasks_dir->close(); |
||
| 201 | } |
||
| 202 | |||
| 203 | // Load all the files in the default template directory - and the current theme if applicable. |
||
| 204 | $directories = array('default_template_versions' => $settings['default_theme_dir']); |
||
| 205 | if ($settings['theme_id'] != 1) |
||
| 206 | $directories += array('template_versions' => $settings['theme_dir']); |
||
| 207 | |||
| 208 | foreach ($directories as $type => $dirname) |
||
| 209 | { |
||
| 210 | $this_dir = dir($dirname); |
||
| 211 | while ($entry = $this_dir->read()) |
||
| 212 | { |
||
| 213 | if (substr($entry, -12) == 'template.php' && !is_dir($dirname . '/' . $entry)) |
||
| 214 | { |
||
| 215 | // Read the first 768 bytes from the file.... enough for the header. |
||
| 216 | $fp = fopen($dirname . '/' . $entry, 'rb'); |
||
| 217 | $header = fread($fp, 768); |
||
| 218 | fclose($fp); |
||
| 219 | |||
| 220 | // Look for the version comment in the file header. |
||
| 221 | if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) |
||
| 222 | $version_info[$type][$entry] = $match[1]; |
||
| 223 | // It wasn't found, but the file was... show a '??'. |
||
| 224 | else |
||
| 225 | $version_info[$type][$entry] = '??'; |
||
| 226 | } |
||
| 227 | } |
||
| 228 | $this_dir->close(); |
||
| 229 | } |
||
| 230 | |||
| 231 | // Load up all the files in the default language directory and sort by language. |
||
| 232 | $this_dir = dir($lang_dir); |
||
| 233 | while ($entry = $this_dir->read()) |
||
| 234 | { |
||
| 235 | if (substr($entry, -4) == '.php' && $entry != 'index.php' && !is_dir($lang_dir . '/' . $entry)) |
||
| 236 | { |
||
| 237 | // Read the first 768 bytes from the file.... enough for the header. |
||
| 238 | $fp = fopen($lang_dir . '/' . $entry, 'rb'); |
||
| 239 | $header = fread($fp, 768); |
||
| 240 | fclose($fp); |
||
| 241 | |||
| 242 | // Split the file name off into useful bits. |
||
| 243 | list ($name, $language) = explode('.', $entry); |
||
| 244 | |||
| 245 | // Look for the version comment in the file header. |
||
| 246 | if (preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '(?:[\s]{2}|\*/)~i', $header, $match) == 1) |
||
| 247 | $version_info['default_language_versions'][$language][$name] = $match[1]; |
||
| 248 | // It wasn't found, but the file was... show a '??'. |
||
| 249 | else |
||
| 250 | $version_info['default_language_versions'][$language][$name] = '??'; |
||
| 251 | } |
||
| 252 | } |
||
| 253 | $this_dir->close(); |
||
| 254 | |||
| 255 | // Sort the file versions by filename. |
||
| 256 | if (!empty($versionOptions['sort_results'])) |
||
| 257 | { |
||
| 258 | ksort($version_info['file_versions']); |
||
| 259 | ksort($version_info['default_template_versions']); |
||
| 260 | ksort($version_info['template_versions']); |
||
| 261 | ksort($version_info['default_language_versions']); |
||
| 262 | ksort($version_info['tasks_versions']); |
||
| 263 | |||
| 264 | // For languages sort each language too. |
||
| 265 | foreach ($version_info['default_language_versions'] as $language => $dummy) |
||
| 266 | ksort($version_info['default_language_versions'][$language]); |
||
| 267 | } |
||
| 268 | return $version_info; |
||
| 269 | } |
||
| 270 | |||
| 271 | /** |
||
| 272 | * Update the Settings.php file. |
||
| 273 | * |
||
| 274 | * The most important function in this file for mod makers happens to be the |
||
| 275 | * updateSettingsFile() function, but it shouldn't be used often anyway. |
||
| 276 | * |
||
| 277 | * - Updates the Settings.php file with the changes supplied in config_vars. |
||
| 278 | * |
||
| 279 | * - Expects config_vars to be an associative array, with the keys as the |
||
| 280 | * variable names in Settings.php, and the values the variable values. |
||
| 281 | * |
||
| 282 | * - Correctly formats the values using smf_var_export(). |
||
| 283 | * |
||
| 284 | * - Restores standard formatting of the file, if $rebuild is true. |
||
| 285 | * |
||
| 286 | * - Checks for changes to db_last_error and passes those off to a separate |
||
| 287 | * handler. |
||
| 288 | * |
||
| 289 | * - Creates a backup file and will use it should the writing of the |
||
| 290 | * new settings file fail. |
||
| 291 | * |
||
| 292 | * - Tries to intelligently trim quotes and remove slashes from string values. |
||
| 293 | * This is done for backwards compatibility purposes (old versions of this |
||
| 294 | * function expected strings to have been manually escaped and quoted). This |
||
| 295 | * behaviour can be controlled by the $keep_quotes parameter. |
||
| 296 | * |
||
| 297 | * @param array $config_vars An array of one or more variables to update. |
||
| 298 | * @param bool|null $keep_quotes Whether to strip slashes & trim quotes from string values. Defaults to auto-detection. |
||
| 299 | * @param bool $rebuild If true, attempts to rebuild with standard format. Default false. |
||
| 300 | * @return bool True on success, false on failure. |
||
| 301 | */ |
||
| 302 | function updateSettingsFile($config_vars, $keep_quotes = null, $rebuild = false) |
||
| 303 | { |
||
| 304 | // In this function we intentionally don't declare any global variables. |
||
| 305 | // This allows us to work with everything cleanly. |
||
| 306 | |||
| 307 | static $mtime; |
||
| 308 | |||
| 309 | // Should we try to unescape the strings? |
||
| 310 | if (empty($keep_quotes)) |
||
| 311 | { |
||
| 312 | foreach ($config_vars as $var => $val) |
||
| 313 | { |
||
| 314 | if (is_string($val) && ($keep_quotes === false || strpos($val, '\'') === 0 && strrpos($val, '\'') === strlen($val) - 1)) |
||
| 315 | $config_vars[$var] = trim(stripcslashes($val), '\''); |
||
| 316 | } |
||
| 317 | } |
||
| 318 | |||
| 319 | // Updating the db_last_error, then don't mess around with Settings.php |
||
| 320 | if (isset($config_vars['db_last_error'])) |
||
| 321 | { |
||
| 322 | updateDbLastError($config_vars['db_last_error']); |
||
| 323 | |||
| 324 | if (count($config_vars) === 1 && empty($rebuild)) |
||
| 325 | return true; |
||
| 326 | |||
| 327 | // Make sure we delete this from Settings.php, if present. |
||
| 328 | $config_vars['db_last_error'] = 0; |
||
| 329 | } |
||
| 330 | |||
| 331 | // Rebuilding should not be undertaken lightly, so we're picky about the parameter. |
||
| 332 | if (!is_bool($rebuild)) |
||
| 333 | $rebuild = false; |
||
| 334 | |||
| 335 | $mtime = isset($mtime) ? (int) $mtime : (defined('TIME_START') ? TIME_START : $_SERVER['REQUEST_TIME']); |
||
| 336 | |||
| 337 | /***************** |
||
| 338 | * PART 1: Setup * |
||
| 339 | *****************/ |
||
| 340 | |||
| 341 | // Typically Settings.php is in $boarddir, but maybe this is a custom setup... |
||
| 342 | foreach (get_included_files() as $settingsFile) |
||
| 343 | if (basename($settingsFile) === 'Settings.php') |
||
| 344 | break; |
||
| 345 | |||
| 346 | // Fallback in case Settings.php isn't loaded (e.g. while installing) |
||
| 347 | if (basename($settingsFile) !== 'Settings.php') |
||
| 348 | $settingsFile = (!empty($GLOBALS['boarddir']) && @realpath($GLOBALS['boarddir']) ? $GLOBALS['boarddir'] : (!empty($_SERVER['SCRIPT_FILENAME']) ? dirname($_SERVER['SCRIPT_FILENAME']) : dirname(__DIR__))) . '/Settings.php'; |
||
| 349 | |||
| 350 | // File not found? Attempt an emergency on-the-fly fix! |
||
| 351 | if (!file_exists($settingsFile)) |
||
| 352 | @touch($settingsFile); |
||
| 353 | |||
| 354 | // When was Settings.php last changed? |
||
| 355 | $last_settings_change = filemtime($settingsFile); |
||
| 356 | |||
| 357 | // Get the current values of everything in Settings.php. |
||
| 358 | $settings_vars = get_current_settings($mtime, $settingsFile); |
||
| 359 | |||
| 360 | // If Settings.php is empty for some reason, see if we can use the backup. |
||
| 361 | if (empty($settings_vars) && file_exists(dirname($settingsFile) . '/Settings_bak.php')) |
||
| 362 | $settings_vars = get_current_settings($mtime, dirname($settingsFile) . '/Settings_bak.php'); |
||
| 363 | |||
| 364 | // False means there was a problem with the file and we can't safely continue. |
||
| 365 | if ($settings_vars === false) |
||
| 366 | return false; |
||
| 367 | |||
| 368 | // It works best to set everything afresh. |
||
| 369 | $new_settings_vars = array_merge($settings_vars, $config_vars); |
||
| 370 | |||
| 371 | // Are we using UTF-8? |
||
| 372 | $utf8 = isset($GLOBALS['context']['utf8']) ? $GLOBALS['context']['utf8'] : (isset($GLOBALS['utf8']) ? $GLOBALS['utf8'] : (isset($settings_vars['db_character_set']) ? $settings_vars['db_character_set'] === 'utf8' : false)); |
||
| 373 | |||
| 374 | /* |
||
| 375 | * A big, fat array to define properties of all the Settings.php variables. |
||
| 376 | * |
||
| 377 | * - String keys are used to identify actual variables. |
||
| 378 | * |
||
| 379 | * - Integer keys are used for content not connected to any particular |
||
| 380 | * variable, such as code blocks or the license block. |
||
| 381 | * |
||
| 382 | * - The content of the 'text' element is simply printed out, if it is used |
||
| 383 | * at all. Use it for comments or to insert code blocks, etc. |
||
| 384 | * |
||
| 385 | * - The 'default' element, not surprisingly, gives a default value for |
||
| 386 | * the variable. |
||
| 387 | * |
||
| 388 | * - The 'type' element defines the expected variable type or types. If |
||
| 389 | * more than one type is allowed, this should be an array listing them. |
||
| 390 | * Types should match the possible types returned by gettype(). |
||
| 391 | * |
||
| 392 | * - If 'raw_default' is true, the default should be printed directly, |
||
| 393 | * rather than being handled as a string. Use it if the default contains |
||
| 394 | * code, e.g. 'dirname(__FILE__)' |
||
| 395 | * |
||
| 396 | * - If 'required' is true and a value for the variable is undefined, |
||
| 397 | * the update will be aborted. (The only exception is during the SMF |
||
| 398 | * installation process.) |
||
| 399 | * |
||
| 400 | * - If 'auto_delete' is 1 or true and the variable is empty, the variable |
||
| 401 | * will be deleted from Settings.php. If 'auto_delete' is 0/false/null, |
||
| 402 | * the variable will never be deleted. If 'auto_delete' is 2, behaviour |
||
| 403 | * depends on $rebuild: if $rebuild is true, 'auto_delete' == 2 behaves |
||
| 404 | * like 'auto_delete' == 1; if $rebuild is false, 'auto_delete' == 2 |
||
| 405 | * behaves like 'auto_delete' == 0. |
||
| 406 | * |
||
| 407 | * - The optional 'search_pattern' element defines a custom regular |
||
| 408 | * expression to search for the existing entry in the file. This is |
||
| 409 | * primarily useful for code blocks rather than variables. |
||
| 410 | * |
||
| 411 | * - The optional 'replace_pattern' element defines a custom regular |
||
| 412 | * expression to decide where the replacement entry should be inserted. |
||
| 413 | * Note: 'replace_pattern' should be avoided unless ABSOLUTELY necessary. |
||
| 414 | */ |
||
| 415 | $settings_defs = array( |
||
| 416 | array( |
||
| 417 | 'text' => implode("\n", array( |
||
| 418 | '', |
||
| 419 | '/**', |
||
| 420 | ' * The settings file contains all of the basic settings that need to be present when a database/cache is not available.', |
||
| 421 | ' *', |
||
| 422 | ' * Simple Machines Forum (SMF)', |
||
| 423 | ' *', |
||
| 424 | ' * @package SMF', |
||
| 425 | ' * @author Simple Machines https://www.simplemachines.org', |
||
| 426 | ' * @copyright ' . SMF_SOFTWARE_YEAR . ' Simple Machines and individual contributors', |
||
| 427 | ' * @license https://www.simplemachines.org/about/smf/license.php BSD', |
||
| 428 | ' *', |
||
| 429 | ' * @version ' . SMF_VERSION, |
||
| 430 | ' */', |
||
| 431 | '', |
||
| 432 | )), |
||
| 433 | 'search_pattern' => '~/\*\*.*?@package\h+SMF\b.*?\*/\n{0,2}~s', |
||
| 434 | ), |
||
| 435 | 'maintenance' => array( |
||
| 436 | 'text' => implode("\n", array( |
||
| 437 | '', |
||
| 438 | '########## Maintenance ##########', |
||
| 439 | '/**', |
||
| 440 | ' * The maintenance "mode"', |
||
| 441 | ' * Set to 1 to enable Maintenance Mode, 2 to make the forum untouchable. (you\'ll have to make it 0 again manually!)', |
||
| 442 | ' * 0 is default and disables maintenance mode.', |
||
| 443 | ' *', |
||
| 444 | ' * @var int 0, 1, 2', |
||
| 445 | ' * @global int $maintenance', |
||
| 446 | ' */', |
||
| 447 | )), |
||
| 448 | 'default' => 0, |
||
| 449 | 'type' => 'integer', |
||
| 450 | ), |
||
| 451 | 'mtitle' => array( |
||
| 452 | 'text' => implode("\n", array( |
||
| 453 | '/**', |
||
| 454 | ' * Title for the Maintenance Mode message.', |
||
| 455 | ' *', |
||
| 456 | ' * @var string', |
||
| 457 | ' * @global int $mtitle', |
||
| 458 | ' */', |
||
| 459 | )), |
||
| 460 | 'default' => 'Maintenance Mode', |
||
| 461 | 'type' => 'string', |
||
| 462 | ), |
||
| 463 | 'mmessage' => array( |
||
| 464 | 'text' => implode("\n", array( |
||
| 465 | '/**', |
||
| 466 | ' * Description of why the forum is in maintenance mode.', |
||
| 467 | ' *', |
||
| 468 | ' * @var string', |
||
| 469 | ' * @global string $mmessage', |
||
| 470 | ' */', |
||
| 471 | )), |
||
| 472 | 'default' => 'Okay faithful users...we\'re attempting to restore an older backup of the database...news will be posted once we\'re back!', |
||
| 473 | 'type' => 'string', |
||
| 474 | ), |
||
| 475 | 'mbname' => array( |
||
| 476 | 'text' => implode("\n", array( |
||
| 477 | '', |
||
| 478 | '########## Forum Info ##########', |
||
| 479 | '/**', |
||
| 480 | ' * The name of your forum.', |
||
| 481 | ' *', |
||
| 482 | ' * @var string', |
||
| 483 | ' */', |
||
| 484 | )), |
||
| 485 | 'default' => 'My Community', |
||
| 486 | 'type' => 'string', |
||
| 487 | ), |
||
| 488 | 'language' => array( |
||
| 489 | 'text' => implode("\n", array( |
||
| 490 | '/**', |
||
| 491 | ' * The default language file set for the forum.', |
||
| 492 | ' *', |
||
| 493 | ' * @var string', |
||
| 494 | ' */', |
||
| 495 | )), |
||
| 496 | 'default' => 'english', |
||
| 497 | 'type' => 'string', |
||
| 498 | ), |
||
| 499 | 'boardurl' => array( |
||
| 500 | 'text' => implode("\n", array( |
||
| 501 | '/**', |
||
| 502 | ' * URL to your forum\'s folder. (without the trailing /!)', |
||
| 503 | ' *', |
||
| 504 | ' * @var string', |
||
| 505 | ' */', |
||
| 506 | )), |
||
| 507 | 'default' => 'http://127.0.0.1/smf', |
||
| 508 | 'type' => 'string', |
||
| 509 | ), |
||
| 510 | 'webmaster_email' => array( |
||
| 511 | 'text' => implode("\n", array( |
||
| 512 | '/**', |
||
| 513 | ' * Email address to send emails from. (like [email protected].)', |
||
| 514 | ' *', |
||
| 515 | ' * @var string', |
||
| 516 | ' */', |
||
| 517 | )), |
||
| 518 | 'default' => '[email protected]', |
||
| 519 | 'type' => 'string', |
||
| 520 | ), |
||
| 521 | 'cookiename' => array( |
||
| 522 | 'text' => implode("\n", array( |
||
| 523 | '/**', |
||
| 524 | ' * Name of the cookie to set for authentication.', |
||
| 525 | ' *', |
||
| 526 | ' * @var string', |
||
| 527 | ' */', |
||
| 528 | )), |
||
| 529 | 'default' => 'SMFCookie11', |
||
| 530 | 'type' => 'string', |
||
| 531 | ), |
||
| 532 | 'auth_secret' => array( |
||
| 533 | 'text' => implode("\n", array( |
||
| 534 | '/**', |
||
| 535 | ' * Secret key used to create and verify cookies, tokens, etc.', |
||
| 536 | ' * Do not change this unless absolutely necessary, and NEVER share it.', |
||
| 537 | ' *', |
||
| 538 | ' * Note: Changing this will immediately log out all members of your forum', |
||
| 539 | ' * and break the token-based links in all previous email notifications,', |
||
| 540 | ' * among other possible effects.', |
||
| 541 | ' *', |
||
| 542 | ' * @var string', |
||
| 543 | ' */', |
||
| 544 | )), |
||
| 545 | 'default' => null, |
||
| 546 | 'auto_delete' => 1, |
||
| 547 | ), |
||
| 548 | 'db_type' => array( |
||
| 549 | 'text' => implode("\n", array( |
||
| 550 | '', |
||
| 551 | '########## Database Info ##########', |
||
| 552 | '/**', |
||
| 553 | ' * The database type', |
||
| 554 | ' * Default options: mysql, postgresql', |
||
| 555 | ' *', |
||
| 556 | ' * @var string', |
||
| 557 | ' */', |
||
| 558 | )), |
||
| 559 | 'default' => 'mysql', |
||
| 560 | 'type' => 'string', |
||
| 561 | ), |
||
| 562 | 'db_port' => array( |
||
| 563 | 'text' => implode("\n", array( |
||
| 564 | '/**', |
||
| 565 | ' * The database port', |
||
| 566 | ' * 0 to use default port for the database type', |
||
| 567 | ' *', |
||
| 568 | ' * @var int', |
||
| 569 | ' */', |
||
| 570 | )), |
||
| 571 | 'default' => 0, |
||
| 572 | 'type' => 'integer', |
||
| 573 | ), |
||
| 574 | 'db_server' => array( |
||
| 575 | 'text' => implode("\n", array( |
||
| 576 | '/**', |
||
| 577 | ' * The server to connect to (or a Unix socket)', |
||
| 578 | ' *', |
||
| 579 | ' * @var string', |
||
| 580 | ' */', |
||
| 581 | )), |
||
| 582 | 'default' => 'localhost', |
||
| 583 | 'required' => true, |
||
| 584 | 'type' => 'string', |
||
| 585 | ), |
||
| 586 | 'db_name' => array( |
||
| 587 | 'text' => implode("\n", array( |
||
| 588 | '/**', |
||
| 589 | ' * The database name', |
||
| 590 | ' *', |
||
| 591 | ' * @var string', |
||
| 592 | ' */', |
||
| 593 | )), |
||
| 594 | 'default' => 'smf', |
||
| 595 | 'required' => true, |
||
| 596 | 'type' => 'string', |
||
| 597 | ), |
||
| 598 | 'db_user' => array( |
||
| 599 | 'text' => implode("\n", array( |
||
| 600 | '/**', |
||
| 601 | ' * Database username', |
||
| 602 | ' *', |
||
| 603 | ' * @var string', |
||
| 604 | ' */', |
||
| 605 | )), |
||
| 606 | 'default' => 'root', |
||
| 607 | 'required' => true, |
||
| 608 | 'type' => 'string', |
||
| 609 | ), |
||
| 610 | 'db_passwd' => array( |
||
| 611 | 'text' => implode("\n", array( |
||
| 612 | '/**', |
||
| 613 | ' * Database password', |
||
| 614 | ' *', |
||
| 615 | ' * @var string', |
||
| 616 | ' */', |
||
| 617 | )), |
||
| 618 | 'default' => '', |
||
| 619 | 'required' => true, |
||
| 620 | 'type' => 'string', |
||
| 621 | ), |
||
| 622 | 'ssi_db_user' => array( |
||
| 623 | 'text' => implode("\n", array( |
||
| 624 | '/**', |
||
| 625 | ' * Database user for when connecting with SSI', |
||
| 626 | ' *', |
||
| 627 | ' * @var string', |
||
| 628 | ' */', |
||
| 629 | )), |
||
| 630 | 'default' => '', |
||
| 631 | 'type' => 'string', |
||
| 632 | ), |
||
| 633 | 'ssi_db_passwd' => array( |
||
| 634 | 'text' => implode("\n", array( |
||
| 635 | '/**', |
||
| 636 | ' * Database password for when connecting with SSI', |
||
| 637 | ' *', |
||
| 638 | ' * @var string', |
||
| 639 | ' */', |
||
| 640 | )), |
||
| 641 | 'default' => '', |
||
| 642 | 'type' => 'string', |
||
| 643 | ), |
||
| 644 | 'db_prefix' => array( |
||
| 645 | 'text' => implode("\n", array( |
||
| 646 | '/**', |
||
| 647 | ' * A prefix to put in front of your table names.', |
||
| 648 | ' * This helps to prevent conflicts', |
||
| 649 | ' *', |
||
| 650 | ' * @var string', |
||
| 651 | ' */', |
||
| 652 | )), |
||
| 653 | 'default' => 'smf_', |
||
| 654 | 'required' => true, |
||
| 655 | 'type' => 'string', |
||
| 656 | ), |
||
| 657 | 'db_persist' => array( |
||
| 658 | 'text' => implode("\n", array( |
||
| 659 | '/**', |
||
| 660 | ' * Use a persistent database connection', |
||
| 661 | ' *', |
||
| 662 | ' * @var bool', |
||
| 663 | ' */', |
||
| 664 | )), |
||
| 665 | 'default' => false, |
||
| 666 | 'type' => 'boolean', |
||
| 667 | ), |
||
| 668 | 'db_error_send' => array( |
||
| 669 | 'text' => implode("\n", array( |
||
| 670 | '/**', |
||
| 671 | ' * Send emails on database connection error', |
||
| 672 | ' *', |
||
| 673 | ' * @var bool', |
||
| 674 | ' */', |
||
| 675 | )), |
||
| 676 | 'default' => false, |
||
| 677 | 'type' => 'boolean', |
||
| 678 | ), |
||
| 679 | 'db_mb4' => array( |
||
| 680 | 'text' => implode("\n", array( |
||
| 681 | '/**', |
||
| 682 | ' * Override the default behavior of the database layer for mb4 handling', |
||
| 683 | ' * null keep the default behavior untouched', |
||
| 684 | ' *', |
||
| 685 | ' * @var null|bool', |
||
| 686 | ' */', |
||
| 687 | )), |
||
| 688 | 'default' => null, |
||
| 689 | 'type' => array('NULL', 'boolean'), |
||
| 690 | ), |
||
| 691 | 'cache_accelerator' => array( |
||
| 692 | 'text' => implode("\n", array( |
||
| 693 | '', |
||
| 694 | '########## Cache Info ##########', |
||
| 695 | '/**', |
||
| 696 | ' * Select a cache system. You want to leave this up to the cache area of the admin panel for', |
||
| 697 | ' * proper detection of apc, memcached, output_cache, smf, or xcache', |
||
| 698 | ' * (you can add more with a mod).', |
||
| 699 | ' *', |
||
| 700 | ' * @var string', |
||
| 701 | ' */', |
||
| 702 | )), |
||
| 703 | 'default' => '', |
||
| 704 | 'type' => 'string', |
||
| 705 | ), |
||
| 706 | 'cache_enable' => array( |
||
| 707 | 'text' => implode("\n", array( |
||
| 708 | '/**', |
||
| 709 | ' * The level at which you would like to cache. Between 0 (off) through 3 (cache a lot).', |
||
| 710 | ' *', |
||
| 711 | ' * @var int', |
||
| 712 | ' */', |
||
| 713 | )), |
||
| 714 | 'default' => 0, |
||
| 715 | 'type' => 'integer', |
||
| 716 | ), |
||
| 717 | 'cache_memcached' => array( |
||
| 718 | 'text' => implode("\n", array( |
||
| 719 | '/**', |
||
| 720 | ' * This is only used for memcache / memcached. Should be a string of \'server:port,server:port\'', |
||
| 721 | ' *', |
||
| 722 | ' * @var array', |
||
| 723 | ' */', |
||
| 724 | )), |
||
| 725 | 'default' => '', |
||
| 726 | 'type' => 'string', |
||
| 727 | ), |
||
| 728 | 'cachedir' => array( |
||
| 729 | 'text' => implode("\n", array( |
||
| 730 | '/**', |
||
| 731 | ' * This is only for the \'smf\' file cache system. It is the path to the cache directory.', |
||
| 732 | ' * It is also recommended that you place this in /tmp/ if you are going to use this.', |
||
| 733 | ' *', |
||
| 734 | ' * @var string', |
||
| 735 | ' */', |
||
| 736 | )), |
||
| 737 | 'default' => 'dirname(__FILE__) . \'/cache\'', |
||
| 738 | 'raw_default' => true, |
||
| 739 | 'type' => 'string', |
||
| 740 | ), |
||
| 741 | 'image_proxy_enabled' => array( |
||
| 742 | 'text' => implode("\n", array( |
||
| 743 | '', |
||
| 744 | '########## Image Proxy ##########', |
||
| 745 | '# This is done entirely in Settings.php to avoid loading the DB while serving the images', |
||
| 746 | '/**', |
||
| 747 | ' * Whether the proxy is enabled or not', |
||
| 748 | ' *', |
||
| 749 | ' * @var bool', |
||
| 750 | ' */', |
||
| 751 | )), |
||
| 752 | 'default' => true, |
||
| 753 | 'type' => 'boolean', |
||
| 754 | ), |
||
| 755 | 'image_proxy_secret' => array( |
||
| 756 | 'text' => implode("\n", array( |
||
| 757 | '/**', |
||
| 758 | ' * Secret key to be used by the proxy', |
||
| 759 | ' *', |
||
| 760 | ' * @var string', |
||
| 761 | ' */', |
||
| 762 | )), |
||
| 763 | 'default' => 'smfisawesome', |
||
| 764 | 'type' => 'string', |
||
| 765 | ), |
||
| 766 | 'image_proxy_maxsize' => array( |
||
| 767 | 'text' => implode("\n", array( |
||
| 768 | '/**', |
||
| 769 | ' * Maximum file size (in KB) for individual files', |
||
| 770 | ' *', |
||
| 771 | ' * @var int', |
||
| 772 | ' */', |
||
| 773 | )), |
||
| 774 | 'default' => 5192, |
||
| 775 | 'type' => 'integer', |
||
| 776 | ), |
||
| 777 | 'boarddir' => array( |
||
| 778 | 'text' => implode("\n", array( |
||
| 779 | '', |
||
| 780 | '########## Directories/Files ##########', |
||
| 781 | '# Note: These directories do not have to be changed unless you move things.', |
||
| 782 | '/**', |
||
| 783 | ' * The absolute path to the forum\'s folder. (not just \'.\'!)', |
||
| 784 | ' *', |
||
| 785 | ' * @var string', |
||
| 786 | ' */', |
||
| 787 | )), |
||
| 788 | 'default' => 'dirname(__FILE__)', |
||
| 789 | 'raw_default' => true, |
||
| 790 | 'type' => 'string', |
||
| 791 | ), |
||
| 792 | 'sourcedir' => array( |
||
| 793 | 'text' => implode("\n", array( |
||
| 794 | '/**', |
||
| 795 | ' * Path to the Sources directory.', |
||
| 796 | ' *', |
||
| 797 | ' * @var string', |
||
| 798 | ' */', |
||
| 799 | )), |
||
| 800 | 'default' => 'dirname(__FILE__) . \'/Sources\'', |
||
| 801 | 'raw_default' => true, |
||
| 802 | 'type' => 'string', |
||
| 803 | ), |
||
| 804 | 'packagesdir' => array( |
||
| 805 | 'text' => implode("\n", array( |
||
| 806 | '/**', |
||
| 807 | ' * Path to the Packages directory.', |
||
| 808 | ' *', |
||
| 809 | ' * @var string', |
||
| 810 | ' */', |
||
| 811 | )), |
||
| 812 | 'default' => 'dirname(__FILE__) . \'/Packages\'', |
||
| 813 | 'raw_default' => true, |
||
| 814 | 'type' => 'string', |
||
| 815 | ), |
||
| 816 | 'tasksdir' => array( |
||
| 817 | 'text' => implode("\n", array( |
||
| 818 | '/**', |
||
| 819 | ' * Path to the tasks directory.', |
||
| 820 | ' *', |
||
| 821 | ' * @var string', |
||
| 822 | ' */', |
||
| 823 | )), |
||
| 824 | 'default' => '$sourcedir . \'/tasks\'', |
||
| 825 | 'raw_default' => true, |
||
| 826 | 'type' => 'string', |
||
| 827 | ), |
||
| 828 | array( |
||
| 829 | 'text' => implode("\n", array( |
||
| 830 | '', |
||
| 831 | '# Make sure the paths are correct... at least try to fix them.', |
||
| 832 | 'if (!is_dir(realpath($boarddir)) && file_exists(dirname(__FILE__) . \'/agreement.txt\'))', |
||
| 833 | ' $boarddir = dirname(__FILE__);', |
||
| 834 | 'if (!is_dir(realpath($sourcedir)) && is_dir($boarddir . \'/Sources\'))', |
||
| 835 | ' $sourcedir = $boarddir . \'/Sources\';', |
||
| 836 | 'if (!is_dir(realpath($tasksdir)) && is_dir($sourcedir . \'/tasks\'))', |
||
| 837 | ' $tasksdir = $sourcedir . \'/tasks\';', |
||
| 838 | 'if (!is_dir(realpath($packagesdir)) && is_dir($boarddir . \'/Packages\'))', |
||
| 839 | ' $packagesdir = $boarddir . \'/Packages\';', |
||
| 840 | 'if (!is_dir(realpath($cachedir)) && is_dir($boarddir . \'/cache\'))', |
||
| 841 | ' $cachedir = $boarddir . \'/cache\';', |
||
| 842 | )), |
||
| 843 | 'search_pattern' => '~\n?(#[^\n]+)?(?:\n\h*if\s*\((?:\!file_exists\(\$(?>boarddir|sourcedir|tasksdir|packagesdir|cachedir)\)|\!is_dir\(realpath\(\$(?>boarddir|sourcedir|tasksdir|packagesdir|cachedir)\)\))[^;]+\n\h*\$(?>boarddir|sourcedir|tasksdir|packagesdir|cachedir)[^\n]+;)+~sm', |
||
| 844 | ), |
||
| 845 | 'db_character_set' => array( |
||
| 846 | 'text' => implode("\n", array( |
||
| 847 | '', |
||
| 848 | '######### Legacy Settings #########', |
||
| 849 | '# UTF-8 is now the only character set supported in 2.1.', |
||
| 850 | )), |
||
| 851 | 'default' => 'utf8', |
||
| 852 | 'type' => 'string', |
||
| 853 | ), |
||
| 854 | 'db_show_debug' => array( |
||
| 855 | 'text' => implode("\n", array( |
||
| 856 | '', |
||
| 857 | '######### Developer Settings #########', |
||
| 858 | '# Show debug info.', |
||
| 859 | )), |
||
| 860 | 'default' => false, |
||
| 861 | 'auto_delete' => 2, |
||
| 862 | 'type' => 'boolean', |
||
| 863 | ), |
||
| 864 | array( |
||
| 865 | 'text' => implode("\n", array( |
||
| 866 | '', |
||
| 867 | '########## Error-Catching ##########', |
||
| 868 | '# Note: You shouldn\'t touch these settings.', |
||
| 869 | 'if (file_exists((isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\'))', |
||
| 870 | ' include((isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\');', |
||
| 871 | '', |
||
| 872 | 'if (!isset($db_last_error))', |
||
| 873 | '{', |
||
| 874 | ' // File does not exist so lets try to create it', |
||
| 875 | ' file_put_contents((isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\', \'<\' . \'?\' . "php\n" . \'$db_last_error = 0;\' . "\n" . \'?\' . \'>\');', |
||
| 876 | ' $db_last_error = 0;', |
||
| 877 | '}', |
||
| 878 | )), |
||
| 879 | // Designed to match both 2.0 and 2.1 versions of this code. |
||
| 880 | 'search_pattern' => '~\n?#+ Error.Catching #+\n[^\n]*?settings\.\n(?:\$db_last_error = \d{1,11};|if \(file_exists.*?\$db_last_error = 0;(?' . '>\s*}))(?=\n|\?' . '>|$)~s', |
||
| 881 | ), |
||
| 882 | // Temporary variable used during the upgrade process. |
||
| 883 | 'upgradeData' => array( |
||
| 884 | 'default' => '', |
||
| 885 | 'auto_delete' => 1, |
||
| 886 | 'type' => 'string', |
||
| 887 | ), |
||
| 888 | // This should be removed if found. |
||
| 889 | 'db_last_error' => array( |
||
| 890 | 'default' => 0, |
||
| 891 | 'auto_delete' => 1, |
||
| 892 | 'type' => 'integer', |
||
| 893 | ), |
||
| 894 | ); |
||
| 895 | |||
| 896 | // Allow mods the option to define comments, defaults, etc., for their settings. |
||
| 897 | // Check if function exists, in case we are calling from installer or upgrader. |
||
| 898 | if (function_exists('call_integration_hook')) |
||
| 899 | call_integration_hook('integrate_update_settings_file', array(&$settings_defs)); |
||
| 900 | |||
| 901 | // If Settings.php is empty or invalid, try to recover using whatever is in $GLOBALS. |
||
| 902 | if ($settings_vars === array()) |
||
| 903 | { |
||
| 904 | foreach ($settings_defs as $var => $setting_def) |
||
| 905 | if (isset($GLOBALS[$var])) |
||
| 906 | $settings_vars[$var] = $GLOBALS[$var]; |
||
| 907 | |||
| 908 | $new_settings_vars = array_merge($settings_vars, $config_vars); |
||
| 909 | } |
||
| 910 | |||
| 911 | // During install/upgrade, don't set anything until we're ready for it. |
||
| 912 | if (defined('SMF_INSTALLING') && empty($rebuild)) |
||
| 913 | { |
||
| 914 | foreach ($settings_defs as $var => $setting_def) |
||
| 915 | if (!in_array($var, array_keys($new_settings_vars)) && !is_int($var)) |
||
| 916 | unset($settings_defs[$var]); |
||
| 917 | } |
||
| 918 | |||
| 919 | /******************************* |
||
| 920 | * PART 2: Build substitutions * |
||
| 921 | *******************************/ |
||
| 922 | |||
| 923 | $type_regex = array( |
||
| 924 | 'string' => |
||
| 925 | '(?:' . |
||
| 926 | // match the opening quotation mark... |
||
| 927 | '(["\'])' . |
||
| 928 | // then any number of other characters or escaped quotation marks... |
||
| 929 | '(?:.(?!\\1)|\\\(?=\\1))*.?' . |
||
| 930 | // then the closing quotation mark. |
||
| 931 | '\\1' . |
||
| 932 | // Maybe there's a second string concatenated to this one. |
||
| 933 | '(?:\s*\.\s*)*' . |
||
| 934 | ')+', |
||
| 935 | // Some numeric values might have been stored as strings. |
||
| 936 | 'integer' => '["\']?[+-]?\d+["\']?', |
||
| 937 | 'double' => '["\']?[+-]?\d+\.\d+([Ee][+-]\d+)?["\']?', |
||
| 938 | // Some boolean values might have been stored as integers. |
||
| 939 | 'boolean' => '(?i:TRUE|FALSE|(["\']?)[01]\b\\1)', |
||
| 940 | 'NULL' => '(?i:NULL)', |
||
| 941 | // These use a PCRE subroutine to match nested arrays. |
||
| 942 | 'array' => 'array\s*(\((?>[^()]|(?1))*\))', |
||
| 943 | 'object' => '\w+::__set_state\(array\s*(\((?>[^()]|(?1))*\))\)', |
||
| 944 | ); |
||
| 945 | |||
| 946 | /* |
||
| 947 | * The substitutions take place in one of two ways: |
||
| 948 | * |
||
| 949 | * 1: The search_pattern regex finds a string in Settings.php, which is |
||
| 950 | * temporarily replaced by a placeholder. Once all the placeholders |
||
| 951 | * have been inserted, each is replaced by the final replacement string |
||
| 952 | * that we want to use. This is the standard method. |
||
| 953 | * |
||
| 954 | * 2: The search_pattern regex finds a string in Settings.php, which is |
||
| 955 | * then deleted by replacing it with an empty placeholder. Then after |
||
| 956 | * all the real placeholders have been dealt with, the replace_pattern |
||
| 957 | * regex finds where to insert the final replacement string that we |
||
| 958 | * want to use. This method is for special cases. |
||
| 959 | */ |
||
| 960 | $prefix = mt_rand() . '-'; |
||
| 961 | $neg_index = -1; |
||
| 962 | $substitutions = array( |
||
| 963 | $neg_index-- => array( |
||
| 964 | 'search_pattern' => '~^\s*<\?(php\b)?\n?~', |
||
| 965 | 'placeholder' => '', |
||
| 966 | 'replace_pattern' => '~^~', |
||
| 967 | 'replacement' => '<' . "?php\n", |
||
| 968 | ), |
||
| 969 | $neg_index-- => array( |
||
| 970 | 'search_pattern' => '~\S\K\s*(\?' . '>)?\s*$~', |
||
| 971 | 'placeholder' => "\n" . md5($prefix . '?' . '>'), |
||
| 972 | 'replacement' => "\n\n?" . '>', |
||
| 973 | ), |
||
| 974 | // Remove the code that redirects to the installer. |
||
| 975 | $neg_index-- => array( |
||
| 976 | 'search_pattern' => '~^if\s*\(file_exists\(dirname\(__FILE__\)\s*\.\s*\'/install\.php\'\)\)\s*(?:({(?>[^{}]|(?1))*})\h*|header(\((?' . '>[^()]|(?2))*\));\n)~m', |
||
| 977 | 'placeholder' => '', |
||
| 978 | ), |
||
| 979 | ); |
||
| 980 | |||
| 981 | if (defined('SMF_INSTALLING')) |
||
| 982 | $substitutions[$neg_index--] = array( |
||
| 983 | 'search_pattern' => '~/\*.*?SMF\s+1\.\d.*?\*/~s', |
||
| 984 | 'placeholder' => '', |
||
| 985 | ); |
||
| 986 | |||
| 987 | foreach ($settings_defs as $var => $setting_def) |
||
| 988 | { |
||
| 989 | $placeholder = md5($prefix . $var); |
||
| 990 | $replacement = ''; |
||
| 991 | |||
| 992 | if (!empty($setting_def['text'])) |
||
| 993 | { |
||
| 994 | // Special handling for the license block: always at the beginning. |
||
| 995 | if (strpos($setting_def['text'], "* @package SMF\n") !== false) |
||
| 996 | { |
||
| 997 | $substitutions[$var]['search_pattern'] = $setting_def['search_pattern']; |
||
| 998 | $substitutions[$var]['placeholder'] = ''; |
||
| 999 | $substitutions[-1]['replacement'] .= $setting_def['text'] . "\n"; |
||
| 1000 | } |
||
| 1001 | // Special handling for the Error-Catching block: always at the end. |
||
| 1002 | elseif (strpos($setting_def['text'], 'Error-Catching') !== false) |
||
| 1003 | { |
||
| 1004 | $errcatch_var = $var; |
||
| 1005 | $substitutions[$var]['search_pattern'] = $setting_def['search_pattern']; |
||
| 1006 | $substitutions[$var]['placeholder'] = ''; |
||
| 1007 | $substitutions[-2]['replacement'] = "\n" . $setting_def['text'] . $substitutions[-2]['replacement']; |
||
| 1008 | } |
||
| 1009 | // The text is the whole thing (code blocks, etc.) |
||
| 1010 | elseif (is_int($var)) |
||
| 1011 | { |
||
| 1012 | // Remember the path correcting code for later. |
||
| 1013 | if (strpos($setting_def['text'], '# Make sure the paths are correct') !== false) |
||
| 1014 | $pathcode_var = $var; |
||
| 1015 | |||
| 1016 | if (!empty($setting_def['search_pattern'])) |
||
| 1017 | $substitutions[$var]['search_pattern'] = $setting_def['search_pattern']; |
||
| 1018 | else |
||
| 1019 | $substitutions[$var]['search_pattern'] = '~' . preg_quote($setting_def['text'], '~') . '~'; |
||
| 1020 | |||
| 1021 | $substitutions[$var]['placeholder'] = $placeholder; |
||
| 1022 | |||
| 1023 | $replacement .= $setting_def['text'] . "\n"; |
||
| 1024 | } |
||
| 1025 | // We only include comments when rebuilding. |
||
| 1026 | elseif (!empty($rebuild)) |
||
| 1027 | $replacement .= $setting_def['text'] . "\n"; |
||
| 1028 | } |
||
| 1029 | |||
| 1030 | if (is_string($var)) |
||
| 1031 | { |
||
| 1032 | // Ensure the value is good. |
||
| 1033 | if (in_array($var, array_keys($new_settings_vars))) |
||
| 1034 | { |
||
| 1035 | // Objects without a __set_state method need a fallback. |
||
| 1036 | if (is_object($new_settings_vars[$var]) && !method_exists($new_settings_vars[$var], '__set_state')) |
||
| 1037 | { |
||
| 1038 | if (method_exists($new_settings_vars[$var], '__toString')) |
||
| 1039 | $new_settings_vars[$var] = (string) $new_settings_vars[$var]; |
||
| 1040 | else |
||
| 1041 | $new_settings_vars[$var] = (array) $new_settings_vars[$var]; |
||
| 1042 | } |
||
| 1043 | |||
| 1044 | // Normalize the type if necessary. |
||
| 1045 | if (isset($setting_def['type'])) |
||
| 1046 | { |
||
| 1047 | $expected_types = (array) $setting_def['type']; |
||
| 1048 | $var_type = gettype($new_settings_vars[$var]); |
||
| 1049 | |||
| 1050 | // Variable is not of an expected type. |
||
| 1051 | if (!in_array($var_type, $expected_types)) |
||
| 1052 | { |
||
| 1053 | // Passed in an unexpected array. |
||
| 1054 | if ($var_type == 'array') |
||
| 1055 | { |
||
| 1056 | $temp = reset($new_settings_vars[$var]); |
||
| 1057 | |||
| 1058 | // Use the first element if there's only one and it is a scalar. |
||
| 1059 | if (count($new_settings_vars[$var]) === 1 && is_scalar($temp)) |
||
| 1060 | $new_settings_vars[$var] = $temp; |
||
| 1061 | |||
| 1062 | // Or keep the old value, if that is good. |
||
| 1063 | elseif (isset($settings_vars[$var]) && in_array(gettype($settings_vars[$var]), $expected_types)) |
||
| 1064 | $new_settings_vars[$var] = $settings_vars[$var]; |
||
| 1065 | |||
| 1066 | // Fall back to the default |
||
| 1067 | else |
||
| 1068 | $new_settings_vars[$var] = $setting_def['default']; |
||
| 1069 | } |
||
| 1070 | |||
| 1071 | // Cast it to whatever type was expected. |
||
| 1072 | // Note: the order of the types in this loop matters. |
||
| 1073 | foreach (array('boolean', 'integer', 'double', 'string', 'array') as $to_type) |
||
| 1074 | { |
||
| 1075 | if (in_array($to_type, $expected_types)) |
||
| 1076 | { |
||
| 1077 | settype($new_settings_vars[$var], $to_type); |
||
| 1078 | break; |
||
| 1079 | } |
||
| 1080 | } |
||
| 1081 | } |
||
| 1082 | } |
||
| 1083 | } |
||
| 1084 | // Abort if a required one is undefined (unless we're installing). |
||
| 1085 | elseif (!empty($setting_def['required']) && !defined('SMF_INSTALLING')) |
||
| 1086 | return false; |
||
| 1087 | |||
| 1088 | // Create the search pattern. |
||
| 1089 | if (!empty($setting_def['search_pattern'])) |
||
| 1090 | $substitutions[$var]['search_pattern'] = $setting_def['search_pattern']; |
||
| 1091 | else |
||
| 1092 | { |
||
| 1093 | $var_pattern = array(); |
||
| 1094 | |||
| 1095 | if (isset($setting_def['type'])) |
||
| 1096 | { |
||
| 1097 | foreach ((array) $setting_def['type'] as $type) |
||
| 1098 | $var_pattern[] = $type_regex[$type]; |
||
| 1099 | } |
||
| 1100 | |||
| 1101 | if (in_array($var, array_keys($config_vars))) |
||
| 1102 | { |
||
| 1103 | $var_pattern[] = @$type_regex[gettype($config_vars[$var])]; |
||
| 1104 | |||
| 1105 | if (is_string($config_vars[$var]) && strpos($config_vars[$var], dirname($settingsFile)) === 0) |
||
| 1106 | $var_pattern[] = '(?:__DIR__|dirname\(__FILE__\)) . \'' . (preg_quote(str_replace(dirname($settingsFile), '', $config_vars[$var]), '~')) . '\''; |
||
| 1107 | } |
||
| 1108 | |||
| 1109 | if (in_array($var, array_keys($settings_vars))) |
||
| 1110 | { |
||
| 1111 | $var_pattern[] = @$type_regex[gettype($settings_vars[$var])]; |
||
| 1112 | |||
| 1113 | if (is_string($settings_vars[$var]) && strpos($settings_vars[$var], dirname($settingsFile)) === 0) |
||
| 1114 | $var_pattern[] = '(?:__DIR__|dirname\(__FILE__\)) . \'' . (preg_quote(str_replace(dirname($settingsFile), '', $settings_vars[$var]), '~')) . '\''; |
||
| 1115 | } |
||
| 1116 | |||
| 1117 | if (!empty($setting_def['raw_default']) && $setting_def['default'] !== '') |
||
| 1118 | { |
||
| 1119 | $var_pattern[] = preg_replace('/\s+/', '\s+', preg_quote($setting_def['default'], '~')); |
||
| 1120 | |||
| 1121 | if (strpos($setting_def['default'], 'dirname(__FILE__)') !== false) |
||
| 1122 | $var_pattern[] = preg_replace('/\s+/', '\s+', preg_quote(str_replace('dirname(__FILE__)', '__DIR__', $setting_def['default']), '~')); |
||
| 1123 | |||
| 1124 | if (strpos($setting_def['default'], '__DIR__') !== false) |
||
| 1125 | $var_pattern[] = preg_replace('/\s+/', '\s+', preg_quote(str_replace('__DIR__', 'dirname(__FILE__)', $setting_def['default']), '~')); |
||
| 1126 | } |
||
| 1127 | |||
| 1128 | $var_pattern = array_unique($var_pattern); |
||
| 1129 | |||
| 1130 | $var_pattern = count($var_pattern) > 1 ? '(?:' . (implode('|', $var_pattern)) . ')' : $var_pattern[0]; |
||
| 1131 | |||
| 1132 | $substitutions[$var]['search_pattern'] = '~(?<=^|\s)\h*\$' . preg_quote($var, '~') . '\s*=\s*' . $var_pattern . ';~' . (!empty($context['utf8']) ? 'u' : ''); |
||
| 1133 | } |
||
| 1134 | |||
| 1135 | // Next create the placeholder or replace_pattern. |
||
| 1136 | if (!empty($setting_def['replace_pattern'])) |
||
| 1137 | $substitutions[$var]['replace_pattern'] = $setting_def['replace_pattern']; |
||
| 1138 | else |
||
| 1139 | $substitutions[$var]['placeholder'] = $placeholder; |
||
| 1140 | |||
| 1141 | // Now create the replacement. |
||
| 1142 | // A setting to delete. |
||
| 1143 | if (!empty($setting_def['auto_delete']) && empty($new_settings_vars[$var])) |
||
| 1144 | { |
||
| 1145 | if ($setting_def['auto_delete'] === 2 && empty($rebuild) && in_array($var, array_keys($new_settings_vars))) |
||
| 1146 | { |
||
| 1147 | $replacement .= '$' . $var . ' = ' . ($new_settings_vars[$var] === $setting_def['default'] && !empty($setting_def['raw_default']) ? sprintf($new_settings_vars[$var]) : smf_var_export($new_settings_vars[$var], true)) . ";"; |
||
| 1148 | } |
||
| 1149 | else |
||
| 1150 | { |
||
| 1151 | $replacement = ''; |
||
| 1152 | |||
| 1153 | // This is just for cosmetic purposes. Removes the blank line. |
||
| 1154 | $substitutions[$var]['search_pattern'] = str_replace('(?<=^|\s)', '\n?', $substitutions[$var]['search_pattern']); |
||
| 1155 | } |
||
| 1156 | } |
||
| 1157 | // Add this setting's value. |
||
| 1158 | elseif (in_array($var, array_keys($new_settings_vars))) |
||
| 1159 | { |
||
| 1160 | $replacement .= '$' . $var . ' = ' . ($new_settings_vars[$var] === $setting_def['default'] && !empty($setting_def['raw_default']) ? sprintf($new_settings_vars[$var]) : smf_var_export($new_settings_vars[$var], true)) . ";"; |
||
| 1161 | } |
||
| 1162 | // Fall back to the default value. |
||
| 1163 | elseif (isset($setting_def['default'])) |
||
| 1164 | { |
||
| 1165 | $replacement .= '$' . $var . ' = ' . (!empty($setting_def['raw_default']) ? sprintf($setting_def['default']) : smf_var_export($setting_def['default'], true)) . ';'; |
||
| 1166 | } |
||
| 1167 | // This shouldn't happen, but we've got nothing. |
||
| 1168 | else |
||
| 1169 | $replacement .= '$' . $var . ' = null;'; |
||
| 1170 | } |
||
| 1171 | |||
| 1172 | $substitutions[$var]['replacement'] = $replacement; |
||
| 1173 | |||
| 1174 | // We're done with this one. |
||
| 1175 | unset($new_settings_vars[$var]); |
||
| 1176 | } |
||
| 1177 | |||
| 1178 | // Any leftovers to deal with? |
||
| 1179 | foreach ($new_settings_vars as $var => $val) |
||
| 1180 | { |
||
| 1181 | $var_pattern = array(); |
||
| 1182 | |||
| 1183 | if (in_array($var, array_keys($config_vars))) |
||
| 1184 | $var_pattern[] = $type_regex[gettype($config_vars[$var])]; |
||
| 1185 | |||
| 1186 | if (in_array($var, array_keys($settings_vars))) |
||
| 1187 | $var_pattern[] = $type_regex[gettype($settings_vars[$var])]; |
||
| 1188 | |||
| 1189 | $var_pattern = array_unique($var_pattern); |
||
| 1190 | |||
| 1191 | $var_pattern = count($var_pattern) > 1 ? '(?:' . (implode('|', $var_pattern)) . ')' : $var_pattern[0]; |
||
| 1192 | |||
| 1193 | $placeholder = md5($prefix . $var); |
||
| 1194 | |||
| 1195 | $substitutions[$var]['search_pattern'] = '~(?<=^|\s)\h*\$' . preg_quote($var, '~') . '\s*=\s*' . $var_pattern . ';~' . (!empty($context['utf8']) ? 'u' : ''); |
||
| 1196 | $substitutions[$var]['placeholder'] = $placeholder; |
||
| 1197 | $substitutions[$var]['replacement'] = '$' . $var . ' = ' . smf_var_export($val, true) . ";"; |
||
| 1198 | } |
||
| 1199 | |||
| 1200 | // During an upgrade, some of the path variables may not have been declared yet. |
||
| 1201 | if (defined('SMF_INSTALLING') && empty($rebuild)) |
||
| 1202 | { |
||
| 1203 | preg_match_all('~^\h*\$(\w+)\s*=\s*~m', $substitutions[$pathcode_var]['replacement'], $matches); |
||
| 1204 | $missing_pathvars = array_diff($matches[1], array_keys($substitutions)); |
||
| 1205 | |||
| 1206 | if (!empty($missing_pathvars)) |
||
| 1207 | { |
||
| 1208 | foreach ($missing_pathvars as $var) |
||
| 1209 | { |
||
| 1210 | $substitutions[$pathcode_var]['replacement'] = preg_replace('~\nif[^\n]+\$' . $var . '[^\n]+\n\h*\$' . $var . ' = [^\n]+~', '', $substitutions[$pathcode_var]['replacement']); |
||
| 1211 | } |
||
| 1212 | } |
||
| 1213 | } |
||
| 1214 | |||
| 1215 | // It's important to do the numbered ones before the named ones, or messes happen. |
||
| 1216 | uksort($substitutions, function($a, $b) { |
||
| 1217 | if (is_int($a) && is_int($b)) |
||
| 1218 | return $a > $b; |
||
| 1219 | elseif (is_int($a)) |
||
| 1220 | return -1; |
||
| 1221 | elseif (is_int($b)) |
||
| 1222 | return 1; |
||
| 1223 | else |
||
| 1224 | return strcasecmp($b, $a); |
||
| 1225 | }); |
||
| 1226 | |||
| 1227 | /****************************** |
||
| 1228 | * PART 3: Content processing * |
||
| 1229 | ******************************/ |
||
| 1230 | |||
| 1231 | /* 3.a: Get the content of Settings.php and make sure it is good. */ |
||
| 1232 | |||
| 1233 | // Retrieve the contents of Settings.php and normalize the line endings. |
||
| 1234 | $settingsText = trim(strtr(file_get_contents($settingsFile), array("\r\n" => "\n", "\r" => "\n"))); |
||
| 1235 | |||
| 1236 | // If Settings.php is empty or corrupt for some reason, see if we can recover. |
||
| 1237 | if ($settingsText == '' || substr($settingsText, 0, 5) !== '<' . '?php') |
||
| 1238 | { |
||
| 1239 | // Try restoring from the backup. |
||
| 1240 | if (file_exists(dirname($settingsFile) . '/Settings_bak.php')) |
||
| 1241 | $settingsText = strtr(file_get_contents(dirname($settingsFile) . '/Settings_bak.php'), array("\r\n" => "\n", "\r" => "\n")); |
||
| 1242 | |||
| 1243 | // Backup is bad too? Our only option is to create one from scratch. |
||
| 1244 | if ($settingsText == '' || substr($settingsText, 0, 5) !== '<' . '?php' || substr($settingsText, -2) !== '?' . '>') |
||
| 1245 | { |
||
| 1246 | $settingsText = '<' . "?php\n"; |
||
| 1247 | foreach ($settings_defs as $var => $setting_def) |
||
| 1248 | { |
||
| 1249 | if (!empty($setting_def['text']) && strpos($substitutions[$var]['replacement'], $setting_def['text']) === false) |
||
| 1250 | $substitutions[$var]['replacement'] = $setting_def['text'] . "\n" . $substitutions[$var]['replacement']; |
||
| 1251 | |||
| 1252 | $settingsText .= $substitutions[$var]['replacement'] . "\n"; |
||
| 1253 | } |
||
| 1254 | $settingsText .= "\n\n?" . '>'; |
||
| 1255 | $rebuild = true; |
||
| 1256 | } |
||
| 1257 | } |
||
| 1258 | |||
| 1259 | // Settings.php is unlikely to contain any heredocs, but just in case... |
||
| 1260 | if (preg_match_all('/<<<(\'?)(\w+)\'?\n(.*?)\n\2;$/m', $settingsText, $matches)) |
||
| 1261 | { |
||
| 1262 | foreach ($matches[0] as $mkey => $heredoc) |
||
| 1263 | { |
||
| 1264 | if (!empty($matches[1][$mkey])) |
||
| 1265 | $heredoc_replacements[$heredoc] = var_export($matches[3][$mkey], true) . ';'; |
||
| 1266 | else |
||
| 1267 | $heredoc_replacements[$heredoc] = '"' . strtr(substr(var_export($matches[3][$mkey], true), 1, -1), array("\\'" => "'", '"' => '\"')) . '";'; |
||
| 1268 | } |
||
| 1269 | |||
| 1270 | $settingsText = strtr($settingsText, $heredoc_replacements); |
||
| 1271 | } |
||
| 1272 | |||
| 1273 | /* 3.b: Loop through all our substitutions to insert placeholders, etc. */ |
||
| 1274 | |||
| 1275 | $last_var = null; |
||
| 1276 | $bare_settingsText = $settingsText; |
||
| 1277 | $force_before_pathcode = array(); |
||
| 1278 | foreach ($substitutions as $var => $substitution) |
||
| 1279 | { |
||
| 1280 | $placeholders[$var] = $substitution['placeholder']; |
||
| 1281 | |||
| 1282 | if (!empty($substitution['placeholder'])) |
||
| 1283 | { |
||
| 1284 | $simple_replacements[$substitution['placeholder']] = $substitution['replacement']; |
||
| 1285 | } |
||
| 1286 | elseif (!empty($substitution['replace_pattern'])) |
||
| 1287 | { |
||
| 1288 | $replace_patterns[$var] = $substitution['replace_pattern']; |
||
| 1289 | $replace_strings[$var] = $substitution['replacement']; |
||
| 1290 | } |
||
| 1291 | |||
| 1292 | if (strpos($substitutions[$pathcode_var]['replacement'], '$' . $var . ' = ') !== false) |
||
| 1293 | $force_before_pathcode[] = $var; |
||
| 1294 | |||
| 1295 | // Look before you leap. |
||
| 1296 | preg_match_all($substitution['search_pattern'], $bare_settingsText, $matches); |
||
| 1297 | |||
| 1298 | if ((is_string($var) || $var === $pathcode_var) && count($matches[0]) !== 1 && $substitution['replacement'] !== '') |
||
| 1299 | { |
||
| 1300 | // More than one instance of the variable = not good. |
||
| 1301 | if (count($matches[0]) > 1) |
||
| 1302 | { |
||
| 1303 | if (is_string($var)) |
||
| 1304 | { |
||
| 1305 | // Maybe we can try something more interesting? |
||
| 1306 | $sp = substr($substitution['search_pattern'], 1); |
||
| 1307 | |||
| 1308 | if (strpos($sp, '(?<=^|\s)') === 0) |
||
| 1309 | $sp = substr($sp, 9); |
||
| 1310 | |||
| 1311 | if (strpos($sp, '^') === 0 || strpos($sp, '(?<') === 0) |
||
| 1312 | return false; |
||
| 1313 | |||
| 1314 | // See if we can exclude `if` blocks, etc., to narrow down the matches. |
||
| 1315 | // @todo Multiple layers of nested brackets might confuse this. |
||
| 1316 | $sp = '~(?:^|//[^\n]+c\n|\*/|[;}]|' . implode('|', array_filter($placeholders)) . ')\s*' . (strpos($sp, '\K') === false ? '\K' : '') . $sp; |
||
| 1317 | |||
| 1318 | preg_match_all($sp, $settingsText, $matches); |
||
| 1319 | } |
||
| 1320 | else |
||
| 1321 | $sp = $substitution['search_pattern']; |
||
| 1322 | |||
| 1323 | // Found at least some that are simple assignment statements. |
||
| 1324 | if (count($matches[0]) > 0) |
||
| 1325 | { |
||
| 1326 | // Remove any duplicates. |
||
| 1327 | if (count($matches[0]) > 1) |
||
| 1328 | $settingsText = preg_replace($sp, '', $settingsText, count($matches[0]) - 1); |
||
| 1329 | |||
| 1330 | // Insert placeholder for the last one. |
||
| 1331 | $settingsText = preg_replace($sp, $substitution['placeholder'], $settingsText, 1); |
||
| 1332 | } |
||
| 1333 | |||
| 1334 | // All instances are inside more complex code structures. |
||
| 1335 | else |
||
| 1336 | { |
||
| 1337 | // Only safe option at this point is to skip it. |
||
| 1338 | unset($substitutions[$var], $new_settings_vars[$var], $settings_defs[$var], $simple_replacements[$substitution['placeholder']], $replace_patterns[$var], $replace_strings[$var]); |
||
| 1339 | |||
| 1340 | continue; |
||
| 1341 | } |
||
| 1342 | } |
||
| 1343 | // No matches found. |
||
| 1344 | elseif (count($matches[0]) === 0) |
||
| 1345 | { |
||
| 1346 | $found = false; |
||
| 1347 | $in_c = in_array($var, array_keys($config_vars)); |
||
| 1348 | $in_s = in_array($var, array_keys($settings_vars)); |
||
| 1349 | |||
| 1350 | // Is it in there at all? |
||
| 1351 | if (!preg_match('~(^|\s)\$' . preg_quote($var, '~') . '\s*=\s*~', $bare_settingsText)) |
||
| 1352 | { |
||
| 1353 | // It's defined by Settings.php, but not by code in the file. |
||
| 1354 | // Probably done via an include or something. Skip it. |
||
| 1355 | if ($in_s) |
||
| 1356 | unset($substitutions[$var], $settings_defs[$var]); |
||
| 1357 | |||
| 1358 | // Admin is explicitly trying to set this one, so we'll handle |
||
| 1359 | // it as if it were a new custom setting being added. |
||
| 1360 | elseif ($in_c) |
||
| 1361 | $new_settings_vars[$var] = $config_vars[$var]; |
||
| 1362 | |||
| 1363 | continue; |
||
| 1364 | } |
||
| 1365 | |||
| 1366 | // It's in there somewhere, so check if the value changed type. |
||
| 1367 | foreach (array('scalar', 'object', 'array') as $type) |
||
| 1368 | { |
||
| 1369 | // Try all the other scalar types first. |
||
| 1370 | if ($type == 'scalar') |
||
| 1371 | $sp = '(?:' . (implode('|', array_diff_key($type_regex, array($in_c ? gettype($config_vars[$var]) : ($in_s ? gettype($settings_vars[$var]) : PHP_INT_MAX) => '', 'array' => '', 'object' => '')))) . ')'; |
||
| 1372 | |||
| 1373 | // Maybe it's an object? (Probably not, but we should check.) |
||
| 1374 | elseif ($type == 'object') |
||
| 1375 | { |
||
| 1376 | if (strpos($settingsText, '__set_state') === false) |
||
| 1377 | continue; |
||
| 1378 | |||
| 1379 | $sp = $type_regex['object']; |
||
| 1380 | } |
||
| 1381 | |||
| 1382 | // Maybe it's an array? |
||
| 1383 | else |
||
| 1384 | $sp = $type_regex['array']; |
||
| 1385 | |||
| 1386 | if (preg_match('~(^|\s)\$' . preg_quote($var, '~') . '\s*=\s*' . $sp . '~', $bare_settingsText, $derp)) |
||
| 1387 | { |
||
| 1388 | $settingsText = preg_replace('~(^|\s)\$' . preg_quote($var, '~') . '\s*=\s*' . $sp . '~', $substitution['placeholder'], $settingsText); |
||
| 1389 | $found = true; |
||
| 1390 | break; |
||
| 1391 | } |
||
| 1392 | } |
||
| 1393 | |||
| 1394 | // Something weird is going on. Better just leave it alone. |
||
| 1395 | if (!$found) |
||
| 1396 | { |
||
| 1397 | // $var? What $var? Never heard of it. |
||
| 1398 | unset($substitutions[$var], $new_settings_vars[$var], $settings_defs[$var], $simple_replacements[$substitution['placeholder']], $replace_patterns[$var], $replace_strings[$var]); |
||
| 1399 | continue; |
||
| 1400 | } |
||
| 1401 | } |
||
| 1402 | } |
||
| 1403 | // Good to go, so insert our placeholder. |
||
| 1404 | else |
||
| 1405 | $settingsText = preg_replace($substitution['search_pattern'], $substitution['placeholder'], $settingsText); |
||
| 1406 | |||
| 1407 | // Once the code blocks are done, we want to compare to a version without comments. |
||
| 1408 | if (is_int($last_var) && is_string($var)) |
||
| 1409 | $bare_settingsText = strip_php_comments($settingsText); |
||
| 1410 | |||
| 1411 | $last_var = $var; |
||
| 1412 | } |
||
| 1413 | |||
| 1414 | // Rebuilding requires more work. |
||
| 1415 | if (!empty($rebuild)) |
||
| 1416 | { |
||
| 1417 | // Strip out the leading and trailing placeholders to prevent duplication. |
||
| 1418 | $settingsText = str_replace(array($substitutions[-1]['placeholder'], $substitutions[-2]['placeholder']), '', $settingsText); |
||
| 1419 | |||
| 1420 | // Strip out all our standard comments. |
||
| 1421 | foreach ($settings_defs as $var => $setting_def) |
||
| 1422 | { |
||
| 1423 | if (isset($setting_def['text'])) |
||
| 1424 | $settingsText = strtr($settingsText, array($setting_def['text'] . "\n" => '', $setting_def['text'] => '',)); |
||
| 1425 | } |
||
| 1426 | |||
| 1427 | // We need to refresh $bare_settingsText at this point. |
||
| 1428 | $bare_settingsText = strip_php_comments($settingsText); |
||
| 1429 | |||
| 1430 | // Fix up whitespace to make comparison easier. |
||
| 1431 | foreach ($placeholders as $placeholder) |
||
| 1432 | { |
||
| 1433 | $bare_settingsText = str_replace(array($placeholder . "\n\n", $placeholder), $placeholder . "\n", $bare_settingsText); |
||
| 1434 | } |
||
| 1435 | $bare_settingsText = preg_replace('/\h+$/m', '', rtrim($bare_settingsText)); |
||
| 1436 | |||
| 1437 | /* |
||
| 1438 | * Divide the existing content into sections. |
||
| 1439 | * The idea here is to make sure we don't mess with the relative position |
||
| 1440 | * of any code blocks in the file, since that could break things. Within |
||
| 1441 | * each section, however, we'll reorganize the content to match the |
||
| 1442 | * default layout as closely as we can. |
||
| 1443 | */ |
||
| 1444 | $sections = array(array()); |
||
| 1445 | $section_num = 0; |
||
| 1446 | $trimmed_placeholders = array_filter(array_map('trim', $placeholders)); |
||
| 1447 | $newsection_placeholders = array(); |
||
| 1448 | $all_custom_content = ''; |
||
| 1449 | foreach ($substitutions as $var => $substitution) |
||
| 1450 | { |
||
| 1451 | if (is_int($var) && ($var === -2 || $var > 0) && isset($trimmed_placeholders[$var]) && strpos($bare_settingsText, $trimmed_placeholders[$var]) !== false) |
||
| 1452 | $newsection_placeholders[$var] = $trimmed_placeholders[$var]; |
||
| 1453 | } |
||
| 1454 | foreach (preg_split('~(?<=' . implode('|', $trimmed_placeholders) . ')|(?=' . implode('|', $trimmed_placeholders) . ')~', $bare_settingsText) as $part) |
||
| 1455 | { |
||
| 1456 | $part = trim($part); |
||
| 1457 | |||
| 1458 | if (empty($part)) |
||
| 1459 | continue; |
||
| 1460 | |||
| 1461 | // Build a list of placeholders for this section. |
||
| 1462 | if (in_array($part, $trimmed_placeholders) && !in_array($part, $newsection_placeholders)) |
||
| 1463 | { |
||
| 1464 | $sections[$section_num][] = $part; |
||
| 1465 | } |
||
| 1466 | // Custom content and newsection_placeholders get their own sections. |
||
| 1467 | else |
||
| 1468 | { |
||
| 1469 | if (!empty($sections[$section_num])) |
||
| 1470 | ++$section_num; |
||
| 1471 | |||
| 1472 | $sections[$section_num][] = $part; |
||
| 1473 | |||
| 1474 | ++$section_num; |
||
| 1475 | |||
| 1476 | if (!in_array($part, $trimmed_placeholders)) |
||
| 1477 | $all_custom_content .= "\n" . $part; |
||
| 1478 | } |
||
| 1479 | } |
||
| 1480 | |||
| 1481 | // And now, rebuild the content! |
||
| 1482 | $new_settingsText = ''; |
||
| 1483 | $done_defs = array(); |
||
| 1484 | $sectionkeys = array_keys($sections); |
||
| 1485 | foreach ($sections as $sectionkey => $section) |
||
| 1486 | { |
||
| 1487 | // Custom content needs to be preserved. |
||
| 1488 | if (count($section) === 1 && !in_array($section[0], $trimmed_placeholders)) |
||
| 1489 | { |
||
| 1490 | $prev_section_end = $sectionkey < 1 ? 0 : strpos($settingsText, end($sections[$sectionkey - 1])) + strlen(end($sections[$sectionkey - 1])); |
||
| 1491 | $next_section_start = $sectionkey == end($sectionkeys) ? strlen($settingsText) : strpos($settingsText, $sections[$sectionkey + 1][0]); |
||
| 1492 | |||
| 1493 | $new_settingsText .= "\n" . substr($settingsText, $prev_section_end, $next_section_start - $prev_section_end) . "\n"; |
||
| 1494 | } |
||
| 1495 | // Put the placeholders in this section into canonical order. |
||
| 1496 | else |
||
| 1497 | { |
||
| 1498 | $section_parts = array_flip($section); |
||
| 1499 | $pathcode_reached = false; |
||
| 1500 | foreach ($settings_defs as $var => $setting_def) |
||
| 1501 | { |
||
| 1502 | if ($var === $pathcode_var) |
||
| 1503 | $pathcode_reached = true; |
||
| 1504 | |||
| 1505 | // Already did this setting, so move on to the next. |
||
| 1506 | if (in_array($var, $done_defs)) |
||
| 1507 | continue; |
||
| 1508 | |||
| 1509 | // Stop when we hit a setting definition that will start a later section. |
||
| 1510 | if (isset($newsection_placeholders[$var]) && count($section) !== 1) |
||
| 1511 | break; |
||
| 1512 | |||
| 1513 | // Stop when everything in this section is done, unless it's the last. |
||
| 1514 | // This helps maintain the relative position of any custom content. |
||
| 1515 | if (empty($section_parts) && $sectionkey < (count($sections) - 1)) |
||
| 1516 | break; |
||
| 1517 | |||
| 1518 | $p = trim($substitutions[$var]['placeholder']); |
||
| 1519 | |||
| 1520 | // Can't do anything with an empty placeholder. |
||
| 1521 | if ($p === '') |
||
| 1522 | continue; |
||
| 1523 | |||
| 1524 | // Does this need to be inserted before the path correction code? |
||
| 1525 | if (strpos($new_settingsText, trim($substitutions[$pathcode_var]['placeholder'])) !== false && in_array($var, $force_before_pathcode)) |
||
| 1526 | { |
||
| 1527 | $new_settingsText = strtr($new_settingsText, array($substitutions[$pathcode_var]['placeholder'] => $p . "\n" . $substitutions[$pathcode_var]['placeholder'])); |
||
| 1528 | |||
| 1529 | $bare_settingsText .= "\n" . $substitutions[$var]['placeholder']; |
||
| 1530 | $done_defs[] = $var; |
||
| 1531 | unset($section_parts[trim($substitutions[$var]['placeholder'])]); |
||
| 1532 | } |
||
| 1533 | |||
| 1534 | // If it's in this section, add it to the new text now. |
||
| 1535 | elseif (in_array($p, $section)) |
||
| 1536 | { |
||
| 1537 | $new_settingsText .= "\n" . $substitutions[$var]['placeholder']; |
||
| 1538 | $done_defs[] = $var; |
||
| 1539 | unset($section_parts[trim($substitutions[$var]['placeholder'])]); |
||
| 1540 | } |
||
| 1541 | |||
| 1542 | // Perhaps it is safe to reposition it anyway. |
||
| 1543 | elseif (is_string($var) && strpos($new_settingsText, $p) === false && strpos($all_custom_content, '$' . $var) === false) |
||
| 1544 | { |
||
| 1545 | $new_settingsText .= "\n" . $substitutions[$var]['placeholder']; |
||
| 1546 | $done_defs[] = $var; |
||
| 1547 | unset($section_parts[trim($substitutions[$var]['placeholder'])]); |
||
| 1548 | } |
||
| 1549 | |||
| 1550 | // If this setting is missing entirely, fix it. |
||
| 1551 | elseif (strpos($bare_settingsText, $p) === false) |
||
| 1552 | { |
||
| 1553 | // Special case if the path code is missing. Put it near the end, |
||
| 1554 | // and also anything else that is missing that normally follows it. |
||
| 1555 | if (!isset($newsection_placeholders[$pathcode_var]) && $pathcode_reached === true && $sectionkey < (count($sections) - 1)) |
||
| 1556 | break; |
||
| 1557 | |||
| 1558 | $new_settingsText .= "\n" . $substitutions[$var]['placeholder']; |
||
| 1559 | $bare_settingsText .= "\n" . $substitutions[$var]['placeholder']; |
||
| 1560 | $done_defs[] = $var; |
||
| 1561 | unset($section_parts[trim($substitutions[$var]['placeholder'])]); |
||
| 1562 | } |
||
| 1563 | } |
||
| 1564 | } |
||
| 1565 | } |
||
| 1566 | $settingsText = $new_settingsText; |
||
| 1567 | |||
| 1568 | // Restore the leading and trailing placeholders as necessary. |
||
| 1569 | foreach (array(-1, -2) as $var) |
||
| 1570 | { |
||
| 1571 | if (!empty($substitutions[$var]['placeholder']) && strpos($settingsText, $substitutions[$var]['placeholder']) === false); |
||
| 1572 | { |
||
| 1573 | $settingsText = ($var == -1 ? $substitutions[$var]['placeholder'] : '') . $settingsText . ($var == -2 ? $substitutions[$var]['placeholder'] : ''); |
||
| 1574 | } |
||
| 1575 | } |
||
| 1576 | } |
||
| 1577 | // Even if not rebuilding, there are a few variables that may need to be moved around. |
||
| 1578 | else |
||
| 1579 | { |
||
| 1580 | $pathcode_pos = strpos($settingsText, $substitutions[$pathcode_var]['placeholder']); |
||
| 1581 | |||
| 1582 | if ($pathcode_pos !== false) |
||
| 1583 | { |
||
| 1584 | foreach ($force_before_pathcode as $var) |
||
| 1585 | { |
||
| 1586 | if (!empty($substitutions[$var]['placeholder']) && strpos($settingsText, $substitutions[$var]['placeholder']) > $pathcode_pos) |
||
| 1587 | { |
||
| 1588 | $settingsText = strtr($settingsText, array( |
||
| 1589 | $substitutions[$var]['placeholder'] => '', |
||
| 1590 | $substitutions[$pathcode_var]['placeholder'] => $substitutions[$var]['placeholder'] . "\n" . $substitutions[$pathcode_var]['placeholder'], |
||
| 1591 | )); |
||
| 1592 | } |
||
| 1593 | } |
||
| 1594 | } |
||
| 1595 | } |
||
| 1596 | |||
| 1597 | /* 3.c: Replace the placeholders with the final values */ |
||
| 1598 | |||
| 1599 | // Where possible, perform simple substitutions. |
||
| 1600 | $settingsText = strtr($settingsText, $simple_replacements); |
||
| 1601 | |||
| 1602 | // Deal with any complicated ones. |
||
| 1603 | if (!empty($replace_patterns)) |
||
| 1604 | $settingsText = preg_replace($replace_patterns, $replace_strings, $settingsText); |
||
| 1605 | |||
| 1606 | // Make absolutely sure that the path correction code is included. |
||
| 1607 | if (strpos($settingsText, $substitutions[$pathcode_var]['replacement']) === false) |
||
| 1608 | $settingsText = preg_replace('~(?=\n#+ Error.Catching #+)~', "\n" . $substitutions[$pathcode_var]['replacement'] . "\n", $settingsText); |
||
| 1609 | |||
| 1610 | // If we did not rebuild, do just enough to make sure the thing is viable. |
||
| 1611 | if (empty($rebuild)) |
||
| 1612 | { |
||
| 1613 | // We need to refresh $bare_settingsText again, and remove the code blocks from it. |
||
| 1614 | $bare_settingsText = $settingsText; |
||
| 1615 | foreach ($substitutions as $var => $substitution) |
||
| 1616 | { |
||
| 1617 | if (!is_int($var)) |
||
| 1618 | break; |
||
| 1619 | |||
| 1620 | if (isset($substitution['replacement'])) |
||
| 1621 | $bare_settingsText = str_replace($substitution['replacement'], '', $bare_settingsText); |
||
| 1622 | } |
||
| 1623 | $bare_settingsText = strip_php_comments($bare_settingsText); |
||
| 1624 | |||
| 1625 | // Now insert any defined settings that are missing. |
||
| 1626 | $pathcode_reached = false; |
||
| 1627 | foreach ($settings_defs as $var => $setting_def) |
||
| 1628 | { |
||
| 1629 | if ($var === $pathcode_var) |
||
| 1630 | $pathcode_reached = true; |
||
| 1631 | |||
| 1632 | if (is_int($var)) |
||
| 1633 | continue; |
||
| 1634 | |||
| 1635 | // Do nothing if it is already in there. |
||
| 1636 | if (preg_match($substitutions[$var]['search_pattern'], $bare_settingsText)) |
||
| 1637 | continue; |
||
| 1638 | |||
| 1639 | // Insert it either before or after the path correction code, whichever is appropriate. |
||
| 1640 | if (!$pathcode_reached || in_array($var, $force_before_pathcode)) |
||
| 1641 | { |
||
| 1642 | $settingsText = preg_replace($substitutions[$pathcode_var]['search_pattern'], $substitutions[$var]['replacement'] . "\n$0", $settingsText); |
||
| 1643 | } |
||
| 1644 | else |
||
| 1645 | { |
||
| 1646 | $settingsText = preg_replace($substitutions[$pathcode_var]['search_pattern'], "$0\n" . $substitutions[$var]['replacement'], $settingsText); |
||
| 1647 | } |
||
| 1648 | } |
||
| 1649 | } |
||
| 1650 | |||
| 1651 | // If we have any brand new settings to add, do so. |
||
| 1652 | foreach ($new_settings_vars as $var => $val) |
||
| 1653 | { |
||
| 1654 | if (isset($substitutions[$var]) && !preg_match($substitutions[$var]['search_pattern'], $settingsText)) |
||
| 1655 | { |
||
| 1656 | if (!isset($settings_defs[$var]) && strpos($settingsText, '# Custom Settings #') === false) |
||
| 1657 | $settingsText = preg_replace('~(?=\n#+ Error.Catching #+)~', "\n\n######### Custom Settings #########\n", $settingsText); |
||
| 1658 | |||
| 1659 | $settingsText = preg_replace('~(?=\n#+ Error.Catching #+)~', $substitutions[$var]['replacement'] . "\n", $settingsText); |
||
| 1660 | } |
||
| 1661 | } |
||
| 1662 | |||
| 1663 | // This is just cosmetic. Get rid of extra lines of whitespace. |
||
| 1664 | $settingsText = preg_replace('~\n\s*\n~', "\n\n", $settingsText); |
||
| 1665 | |||
| 1666 | /************************************** |
||
| 1667 | * PART 4: Check syntax before saving * |
||
| 1668 | **************************************/ |
||
| 1669 | |||
| 1670 | $temp_sfile = tempnam(sm_temp_dir(), md5($prefix . 'Settings.php')); |
||
| 1671 | file_put_contents($temp_sfile, $settingsText); |
||
| 1672 | |||
| 1673 | $result = get_current_settings(filemtime($temp_sfile), $temp_sfile); |
||
| 1674 | |||
| 1675 | unlink($temp_sfile); |
||
| 1676 | |||
| 1677 | // If the syntax is borked, try rebuilding to see if that fixes it. |
||
| 1678 | if ($result === false) |
||
| 1679 | return empty($rebuild) ? updateSettingsFile($config_vars, $keep_quotes, true) : false; |
||
| 1680 | |||
| 1681 | /****************************************** |
||
| 1682 | * PART 5: Write updated settings to file * |
||
| 1683 | ******************************************/ |
||
| 1684 | |||
| 1685 | $success = safe_file_write($settingsFile, $settingsText, dirname($settingsFile) . '/Settings_bak.php', $last_settings_change); |
||
| 1686 | |||
| 1687 | // Remember this in case updateSettingsFile is called twice. |
||
| 1688 | $mtime = filemtime($settingsFile); |
||
| 1689 | |||
| 1690 | return $success; |
||
| 1691 | } |
||
| 1692 | |||
| 1693 | /** |
||
| 1694 | * Retrieves a copy of the current values of all settings defined in Settings.php. |
||
| 1695 | * |
||
| 1696 | * Importantly, it does this without affecting our actual global variables at all, |
||
| 1697 | * and it performs safety checks before acting. The result is an array of the |
||
| 1698 | * values as recorded in the settings file. |
||
| 1699 | * |
||
| 1700 | * @param int $mtime Timestamp of last known good configuration. Defaults to time SMF started. |
||
| 1701 | * @param string $settingsFile The settings file. Defaults to SMF's standard Settings.php. |
||
| 1702 | * @return array An array of name/value pairs for all the settings in the file. |
||
| 1703 | */ |
||
| 1704 | function get_current_settings($mtime = null, $settingsFile = null) |
||
| 1705 | { |
||
| 1706 | $mtime = is_null($mtime) ? (defined('TIME_START') ? TIME_START : $_SERVER['REQUEST_TIME']) : (int) $mtime; |
||
| 1707 | |||
| 1708 | if (!is_file($settingsFile)) |
||
| 1709 | { |
||
| 1710 | foreach (get_included_files() as $settingsFile) |
||
| 1711 | if (basename($settingsFile) === 'Settings.php') |
||
| 1712 | break; |
||
| 1713 | |||
| 1714 | if (basename($settingsFile) !== 'Settings.php') |
||
| 1715 | return false; |
||
| 1716 | } |
||
| 1717 | |||
| 1718 | // If the file has been changed since the last known good configuration, bail out. |
||
| 1719 | clearstatcache(); |
||
| 1720 | if (filemtime($settingsFile) > $mtime) |
||
| 1721 | return false; |
||
| 1722 | |||
| 1723 | // Strip out opening and closing PHP tags. |
||
| 1724 | $settingsText = trim(file_get_contents($settingsFile)); |
||
| 1725 | if (substr($settingsText, 0, 5) == '<' . '?php') |
||
| 1726 | $settingsText = substr($settingsText, 5); |
||
| 1727 | if (substr($settingsText, -2) == '?' . '>') |
||
| 1728 | $settingsText = substr($settingsText, 0, -2); |
||
| 1729 | |||
| 1730 | // Since we're using eval, we need to manually replace these with strings. |
||
| 1731 | $settingsText = strtr($settingsText, array( |
||
| 1732 | '__FILE__' => var_export($settingsFile, true), |
||
| 1733 | '__DIR__' => var_export(dirname($settingsFile), true), |
||
| 1734 | )); |
||
| 1735 | |||
| 1736 | // Prevents warnings about constants that are already defined. |
||
| 1737 | $settingsText = preg_replace_callback( |
||
| 1738 | '~\bdefine\s*\(\s*(["\'])(\w+)\1~', |
||
| 1739 | function ($matches) |
||
| 1740 | { |
||
| 1741 | return 'define(\'' . md5(mt_rand()) . '\''; |
||
| 1742 | }, |
||
| 1743 | $settingsText |
||
| 1744 | ); |
||
| 1745 | |||
| 1746 | // Handle eval errors gracefully in both PHP 5 and PHP 7 |
||
| 1747 | try |
||
| 1748 | { |
||
| 1749 | if($settingsText !== '' && @eval($settingsText) === false) |
||
| 1750 | throw new ErrorException('eval error'); |
||
| 1751 | |||
| 1752 | unset($mtime, $settingsFile, $settingsText); |
||
| 1753 | $defined_vars = get_defined_vars(); |
||
| 1754 | } |
||
| 1755 | catch (Throwable $e) {} |
||
| 1756 | catch (ErrorException $e) {} |
||
| 1757 | if (isset($e)) |
||
| 1758 | return false; |
||
| 1759 | |||
| 1760 | return $defined_vars; |
||
| 1761 | } |
||
| 1762 | |||
| 1763 | /** |
||
| 1764 | * Writes data to a file, optionally making a backup, while avoiding race conditions. |
||
| 1765 | * |
||
| 1766 | * @param string $file The filepath of the file where the data should be written. |
||
| 1767 | * @param string $data The data to be written to $file. |
||
| 1768 | * @param string $backup_file The filepath where the backup should be saved. Default null. |
||
| 1769 | * @param int $mtime If modification time of $file is more recent than this Unix timestamp, the write operation will abort. Defaults to time that the script started execution. |
||
| 1770 | * @param bool $append If true, the data will be appended instead of overwriting the existing content of the file. Default false. |
||
| 1771 | * @return bool Whether the write operation succeeded or not. |
||
| 1772 | */ |
||
| 1773 | function safe_file_write($file, $data, $backup_file = null, $mtime = null, $append = false) |
||
| 1774 | { |
||
| 1775 | global $cachedir; |
||
| 1776 | |||
| 1777 | // Sanity checks. |
||
| 1778 | if (!file_exists($file) && !is_dir(dirname($file))) |
||
| 1779 | return false; |
||
| 1780 | |||
| 1781 | if (!is_int($mtime)) |
||
| 1782 | $mtime = $_SERVER['REQUEST_TIME']; |
||
| 1783 | |||
| 1784 | $temp_dir = sm_temp_dir(); |
||
| 1785 | |||
| 1786 | // Our temp files. |
||
| 1787 | $temp_sfile = tempnam($temp_dir, pathinfo($file, PATHINFO_FILENAME) . '.'); |
||
| 1788 | |||
| 1789 | if (!empty($backup_file)) |
||
| 1790 | $temp_bfile = tempnam($temp_dir, pathinfo($backup_file, PATHINFO_FILENAME) . '.'); |
||
| 1791 | |||
| 1792 | // We need write permissions. |
||
| 1793 | $failed = false; |
||
| 1794 | foreach (array($file, $backup_file) as $sf) |
||
| 1795 | { |
||
| 1796 | if (empty($sf)) |
||
| 1797 | continue; |
||
| 1798 | |||
| 1799 | if (!file_exists($sf)) |
||
| 1800 | touch($sf); |
||
| 1801 | elseif (!is_file($sf)) |
||
| 1802 | $failed = true; |
||
| 1803 | |||
| 1804 | if (!$failed) |
||
| 1805 | $failed = !smf_chmod($sf); |
||
| 1806 | } |
||
| 1807 | |||
| 1808 | // Is there enough free space on the disk? |
||
| 1809 | if (!$failed && disk_free_space(dirname($file)) < (strlen($data) + filesize($file) + (!empty($backup_file) ? filesize($backup_file) : 0))) |
||
| 1810 | $failed = true; |
||
| 1811 | |||
| 1812 | // Now let's see if writing to a temp file succeeds. |
||
| 1813 | if (!$failed && file_put_contents($temp_sfile, $data, LOCK_EX) !== strlen($data)) |
||
| 1814 | $failed = true; |
||
| 1815 | |||
| 1816 | // Tests passed, so it's time to do the job. |
||
| 1817 | if (!$failed) |
||
| 1818 | { |
||
| 1819 | // Back up the backup, just in case. |
||
| 1820 | if (file_exists($backup_file)) |
||
| 1821 | $temp_bfile_saved = @copy($backup_file, $temp_bfile); |
||
| 1822 | |||
| 1823 | // Make sure no one changed the file while we weren't looking. |
||
| 1824 | clearstatcache(); |
||
| 1825 | if (filemtime($file) <= $mtime) |
||
| 1826 | { |
||
| 1827 | // Attempt to open the file. |
||
| 1828 | $sfhandle = @fopen($file, 'c'); |
||
| 1829 | |||
| 1830 | // Let's do this thing! |
||
| 1831 | if ($sfhandle !== false) |
||
| 1832 | { |
||
| 1833 | // Immediately get a lock. |
||
| 1834 | flock($sfhandle, LOCK_EX); |
||
| 1835 | |||
| 1836 | // Make sure the backup works before we do anything more. |
||
| 1837 | $temp_sfile_saved = @copy($file, $temp_sfile); |
||
| 1838 | |||
| 1839 | // Now write our data to the file. |
||
| 1840 | if ($temp_sfile_saved) |
||
| 1841 | { |
||
| 1842 | if (empty($append)) |
||
| 1843 | { |
||
| 1844 | ftruncate($sfhandle, 0); |
||
| 1845 | rewind($sfhandle); |
||
| 1846 | } |
||
| 1847 | |||
| 1848 | $failed = fwrite($sfhandle, $data) !== strlen($data); |
||
| 1849 | } |
||
| 1850 | else |
||
| 1851 | $failed = true; |
||
| 1852 | |||
| 1853 | // If writing failed, put everything back the way it was. |
||
| 1854 | if ($failed) |
||
| 1855 | { |
||
| 1856 | if (!empty($temp_sfile_saved)) |
||
| 1857 | @rename($temp_sfile, $file); |
||
| 1858 | |||
| 1859 | if (!empty($temp_bfile_saved)) |
||
| 1860 | @rename($temp_bfile, $backup_file); |
||
| 1861 | } |
||
| 1862 | // It worked, so make our temp backup the new permanent backup. |
||
| 1863 | elseif (!empty($backup_file)) |
||
| 1864 | @rename($temp_sfile, $backup_file); |
||
| 1865 | |||
| 1866 | // And we're done. |
||
| 1867 | flock($sfhandle, LOCK_UN); |
||
| 1868 | fclose($sfhandle); |
||
| 1869 | } |
||
| 1870 | } |
||
| 1871 | } |
||
| 1872 | |||
| 1873 | // We're done with these. |
||
| 1874 | @unlink($temp_sfile); |
||
| 1875 | @unlink($temp_bfile); |
||
| 1876 | |||
| 1877 | if ($failed) |
||
| 1878 | return false; |
||
| 1879 | |||
| 1880 | // Even though on normal installations the filemtime should invalidate any cached version |
||
| 1881 | // it seems that there are times it might not. So let's MAKE it dump the cache. |
||
| 1882 | if (function_exists('opcache_invalidate')) |
||
| 1883 | opcache_invalidate($file, true); |
||
| 1884 | |||
| 1885 | return true; |
||
| 1886 | } |
||
| 1887 | |||
| 1888 | /** |
||
| 1889 | * A wrapper around var_export whose output matches SMF coding conventions. |
||
| 1890 | * |
||
| 1891 | * @todo Add special handling for objects? |
||
| 1892 | * |
||
| 1893 | * @param mixed $var The variable to export |
||
| 1894 | * @return mixed A PHP-parseable representation of the variable's value |
||
| 1895 | */ |
||
| 1896 | function smf_var_export($var) |
||
| 1897 | { |
||
| 1898 | /* |
||
| 1899 | * Old versions of updateSettingsFile couldn't handle multi-line values. |
||
| 1900 | * Even though technically we can now, we'll keep arrays on one line for |
||
| 1901 | * the sake of backwards compatibility. |
||
| 1902 | */ |
||
| 1903 | if (is_array($var)) |
||
| 1904 | { |
||
| 1905 | $return = array(); |
||
| 1906 | |||
| 1907 | foreach ($var as $key => $value) |
||
| 1908 | $return[] = var_export($key, true) . ' => ' . smf_var_export($value); |
||
| 1909 | |||
| 1910 | return 'array(' . implode(', ', $return) . ')'; |
||
| 1911 | } |
||
| 1912 | |||
| 1913 | // For the same reason, replace literal returns and newlines with "\r" and "\n" |
||
| 1914 | elseif (is_string($var) && (strpos($var, "\n") !== false || strpos($var, "\r") !== false)) |
||
| 1915 | { |
||
| 1916 | return strtr(preg_replace_callback('/[\r\n]+/', function($m) { |
||
| 1917 | return '\' . "' . strtr($m[0], array("\r" => '\r', "\n" => '\n')) . '" . \''; |
||
| 1918 | }, $var), array("'' . " => '', " . ''" => '')); |
||
| 1919 | } |
||
| 1920 | |||
| 1921 | // We typically use lowercase true/false/null. |
||
| 1922 | elseif (in_array(gettype($var), array('boolean', 'NULL'))) |
||
| 1923 | return strtolower(var_export($var, true)); |
||
| 1924 | |||
| 1925 | // Nothing special. |
||
| 1926 | else |
||
| 1927 | return var_export($var, true); |
||
| 1928 | }; |
||
| 1929 | |||
| 1930 | /** |
||
| 1931 | * Deletes all PHP comments from a string. |
||
| 1932 | * Useful when analyzing input from file_get_contents, etc. |
||
| 1933 | * |
||
| 1934 | * If the code contains any strings in nowdoc or heredoc syntax, they will be |
||
| 1935 | * converted to single- or double-quote strings. |
||
| 1936 | * |
||
| 1937 | * @param string $code_str A string containing PHP code. |
||
| 1938 | * @param string|null $line_ending One of "\r", "\n", or "\r\n". Leave unset for auto-detect. |
||
| 1939 | * @return string A string of PHP code with no comments in it. |
||
| 1940 | */ |
||
| 1941 | function strip_php_comments($code_str, $line_ending = null) |
||
| 1942 | { |
||
| 1943 | // What line ending should we use? |
||
| 1944 | // Note: this depends on the string, not the host OS, so PHP_EOL isn't what we want. |
||
| 1945 | if (!in_array($line_ending, array("\r", "\n", "\r\n"))) |
||
| 1946 | { |
||
| 1947 | if (strpos($code_str, "\r\n") !== false) |
||
| 1948 | $line_ending = "\r\n"; |
||
| 1949 | elseif (strpos($code_str, "\n") !== false) |
||
| 1950 | $line_ending = "\n"; |
||
| 1951 | elseif (strpos($code_str, "\r") !== false) |
||
| 1952 | $line_ending = "\r"; |
||
| 1953 | } |
||
| 1954 | |||
| 1955 | // Everything is simpler if we convert heredocs to normal strings first. |
||
| 1956 | if (preg_match_all('/<<<(\'?)(\w+)\'?'. $line_ending . '(.*?)'. $line_ending . '\2;$/m', $code_str, $matches)) |
||
| 1957 | { |
||
| 1958 | foreach ($matches[0] as $mkey => $heredoc) |
||
| 1959 | { |
||
| 1960 | if (!empty($matches[1][$mkey])) |
||
| 1961 | $heredoc_replacements[$heredoc] = var_export($matches[3][$mkey], true) . ';'; |
||
| 1962 | else |
||
| 1963 | $heredoc_replacements[$heredoc] = '"' . strtr(substr(var_export($matches[3][$mkey], true), 1, -1), array("\\'" => "'", '"' => '\"')) . '";'; |
||
| 1964 | } |
||
| 1965 | |||
| 1966 | $code_str = strtr($code_str, $heredoc_replacements); |
||
| 1967 | } |
||
| 1968 | |||
| 1969 | // Split before everything that could possibly delimit a comment or a string. |
||
| 1970 | $parts = preg_split('~(?=#+|/(?=/|\*)|\*/|'. $line_ending . '|(?<!\\\)[\'"])~m', $code_str); |
||
| 1971 | |||
| 1972 | $in_string = 0; |
||
| 1973 | $in_comment = 0; |
||
| 1974 | foreach ($parts as $partkey => $part) |
||
| 1975 | { |
||
| 1976 | $one_char = substr($part, 0, 1); |
||
| 1977 | $two_char = substr($part, 0, 2); |
||
| 1978 | $to_remove = 0; |
||
| 1979 | |||
| 1980 | /* |
||
| 1981 | * Meaning of $in_string values: |
||
| 1982 | * 0: not in a string |
||
| 1983 | * 1: in a single quote string |
||
| 1984 | * 2: in a double quote string |
||
| 1985 | */ |
||
| 1986 | if ($one_char == "'") |
||
| 1987 | { |
||
| 1988 | if (!empty($in_comment)) |
||
| 1989 | $in_string = 0; |
||
| 1990 | elseif (in_array($in_string, array(0, 1))) |
||
| 1991 | $in_string = ($in_string ^ 1); |
||
| 1992 | } |
||
| 1993 | elseif ($one_char == '"') |
||
| 1994 | { |
||
| 1995 | if (!empty($in_comment)) |
||
| 1996 | $in_string = 0; |
||
| 1997 | elseif (in_array($in_string, array(0, 2))) |
||
| 1998 | $in_string = ($in_string ^ 2); |
||
| 1999 | } |
||
| 2000 | |||
| 2001 | /* |
||
| 2002 | * Meaning of $in_comment values: |
||
| 2003 | * 0: not in a comment |
||
| 2004 | * 1: in a single line comment |
||
| 2005 | * 2: in a multi-line comment |
||
| 2006 | */ |
||
| 2007 | elseif ($one_char == '#' || $two_char == '//') |
||
| 2008 | { |
||
| 2009 | $in_comment = !empty($in_string) ? 0 : (empty($in_comment) ? 1 : $in_comment); |
||
| 2010 | } |
||
| 2011 | elseif (($line_ending === "\r\n" && $two_char === $line_ending) || $one_char == $line_ending) |
||
| 2012 | { |
||
| 2013 | if ($in_comment == 1) |
||
| 2014 | { |
||
| 2015 | $in_comment = 0; |
||
| 2016 | |||
| 2017 | // If we've removed everything on this line, take out the line ending, too. |
||
| 2018 | if ($parts[$partkey - 1] === $line_ending) |
||
| 2019 | $to_remove = strlen($line_ending); |
||
| 2020 | } |
||
| 2021 | } |
||
| 2022 | elseif ($two_char == '/*') |
||
| 2023 | { |
||
| 2024 | $in_comment = !empty($in_string) ? 0 : (empty($in_comment) ? 2 : $in_comment); |
||
| 2025 | } |
||
| 2026 | elseif ($two_char == '*/') |
||
| 2027 | { |
||
| 2028 | if ($in_comment == 2) |
||
| 2029 | { |
||
| 2030 | $in_comment = 0; |
||
| 2031 | |||
| 2032 | // Delete the comment closing. |
||
| 2033 | $to_remove = 2; |
||
| 2034 | } |
||
| 2035 | } |
||
| 2036 | |||
| 2037 | if (empty($in_comment)) |
||
| 2038 | $parts[$partkey] = strlen($part) > $to_remove ? substr($part, $to_remove) : ''; |
||
| 2039 | else |
||
| 2040 | $parts[$partkey] = ''; |
||
| 2041 | } |
||
| 2042 | |||
| 2043 | return implode('', $parts); |
||
| 2044 | } |
||
| 2045 | |||
| 2046 | /** |
||
| 2047 | * Saves the time of the last db error for the error log |
||
| 2048 | * - Done separately from updateSettingsFile to avoid race conditions |
||
| 2049 | * which can occur during a db error |
||
| 2050 | * - If it fails Settings.php will assume 0 |
||
| 2051 | * |
||
| 2052 | * @param int $time The timestamp of the last DB error |
||
| 2053 | */ |
||
| 2054 | function updateDbLastError($time) |
||
| 2055 | { |
||
| 2056 | global $boarddir, $cachedir; |
||
| 2057 | |||
| 2058 | // Write out the db_last_error file with the error timestamp |
||
| 2059 | if (!empty($cachedir) && is_writable($cachedir)) |
||
| 2060 | $errorfile = $cachedir . '/db_last_error.php'; |
||
| 2061 | |||
| 2062 | elseif (file_exists(dirname(__DIR__) . '/cache')) |
||
| 2063 | $errorfile = dirname(__DIR__) . '/cache/db_last_error.php'; |
||
| 2064 | |||
| 2065 | else |
||
| 2066 | $errorfile = dirname(__DIR__) . '/db_last_error.php'; |
||
| 2067 | |||
| 2068 | file_put_contents($errorfile, '<' . '?' . "php\n" . '$db_last_error = ' . $time . ';' . "\n" . '?' . '>', LOCK_EX); |
||
| 2069 | |||
| 2070 | @touch($boarddir . '/' . 'Settings.php'); |
||
| 2071 | } |
||
| 2072 | |||
| 2073 | /** |
||
| 2074 | * Saves the admin's current preferences to the database. |
||
| 2075 | */ |
||
| 2076 | function updateAdminPreferences() |
||
| 2077 | { |
||
| 2078 | global $options, $context, $smcFunc, $settings, $user_info; |
||
| 2079 | |||
| 2080 | // This must exist! |
||
| 2081 | if (!isset($context['admin_preferences'])) |
||
| 2082 | return false; |
||
| 2083 | |||
| 2084 | // This is what we'll be saving. |
||
| 2085 | $options['admin_preferences'] = $smcFunc['json_encode']($context['admin_preferences']); |
||
| 2086 | |||
| 2087 | // Just check we haven't ended up with something theme exclusive somehow. |
||
| 2088 | $smcFunc['db_query']('', ' |
||
| 2089 | DELETE FROM {db_prefix}themes |
||
| 2090 | WHERE id_theme != {int:default_theme} |
||
| 2091 | AND variable = {string:admin_preferences}', |
||
| 2092 | array( |
||
| 2093 | 'default_theme' => 1, |
||
| 2094 | 'admin_preferences' => 'admin_preferences', |
||
| 2095 | ) |
||
| 2096 | ); |
||
| 2097 | |||
| 2098 | // Update the themes table. |
||
| 2099 | $smcFunc['db_insert']('replace', |
||
| 2100 | '{db_prefix}themes', |
||
| 2101 | array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), |
||
| 2102 | array($user_info['id'], 1, 'admin_preferences', $options['admin_preferences']), |
||
| 2103 | array('id_member', 'id_theme', 'variable') |
||
| 2104 | ); |
||
| 2105 | |||
| 2106 | // Make sure we invalidate any cache. |
||
| 2107 | cache_put_data('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id'], null, 0); |
||
| 2108 | } |
||
| 2109 | |||
| 2110 | /** |
||
| 2111 | * Send all the administrators a lovely email. |
||
| 2112 | * - loads all users who are admins or have the admin forum permission. |
||
| 2113 | * - uses the email template and replacements passed in the parameters. |
||
| 2114 | * - sends them an email. |
||
| 2115 | * |
||
| 2116 | * @param string $template Which email template to use |
||
| 2117 | * @param array $replacements An array of items to replace the variables in the template |
||
| 2118 | * @param array $additional_recipients An array of arrays of info for additional recipients. Should have 'id', 'email' and 'name' for each. |
||
| 2119 | */ |
||
| 2120 | function emailAdmins($template, $replacements = array(), $additional_recipients = array()) |
||
| 2121 | { |
||
| 2122 | global $smcFunc, $sourcedir, $language, $modSettings; |
||
| 2123 | |||
| 2124 | // We certainly want this. |
||
| 2125 | require_once($sourcedir . '/Subs-Post.php'); |
||
| 2126 | |||
| 2127 | // Load all members which are effectively admins. |
||
| 2128 | require_once($sourcedir . '/Subs-Members.php'); |
||
| 2129 | $members = membersAllowedTo('admin_forum'); |
||
| 2130 | |||
| 2131 | // Load their alert preferences |
||
| 2132 | require_once($sourcedir . '/Subs-Notify.php'); |
||
| 2133 | $prefs = getNotifyPrefs($members, 'announcements', true); |
||
| 2134 | |||
| 2135 | $request = $smcFunc['db_query']('', ' |
||
| 2136 | SELECT id_member, member_name, real_name, lngfile, email_address |
||
| 2137 | FROM {db_prefix}members |
||
| 2138 | WHERE id_member IN({array_int:members})', |
||
| 2139 | array( |
||
| 2140 | 'members' => $members, |
||
| 2141 | ) |
||
| 2142 | ); |
||
| 2143 | $emails_sent = array(); |
||
| 2144 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||
| 2145 | { |
||
| 2146 | if (empty($prefs[$row['id_member']]['announcements'])) |
||
| 2147 | continue; |
||
| 2148 | |||
| 2149 | // Stick their particulars in the replacement data. |
||
| 2150 | $replacements['IDMEMBER'] = $row['id_member']; |
||
| 2151 | $replacements['REALNAME'] = $row['member_name']; |
||
| 2152 | $replacements['USERNAME'] = $row['real_name']; |
||
| 2153 | |||
| 2154 | // Load the data from the template. |
||
| 2155 | $emaildata = loadEmailTemplate($template, $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']); |
||
| 2156 | |||
| 2157 | // Then send the actual email. |
||
| 2158 | sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, $template, $emaildata['is_html'], 1); |
||
| 2159 | |||
| 2160 | // Track who we emailed so we don't do it twice. |
||
| 2161 | $emails_sent[] = $row['email_address']; |
||
| 2162 | } |
||
| 2163 | $smcFunc['db_free_result']($request); |
||
| 2164 | |||
| 2165 | // Any additional users we must email this to? |
||
| 2166 | if (!empty($additional_recipients)) |
||
| 2167 | foreach ($additional_recipients as $recipient) |
||
| 2168 | { |
||
| 2169 | if (in_array($recipient['email'], $emails_sent)) |
||
| 2170 | continue; |
||
| 2171 | |||
| 2172 | $replacements['IDMEMBER'] = $recipient['id']; |
||
| 2173 | $replacements['REALNAME'] = $recipient['name']; |
||
| 2174 | $replacements['USERNAME'] = $recipient['name']; |
||
| 2175 | |||
| 2176 | // Load the template again. |
||
| 2177 | $emaildata = loadEmailTemplate($template, $replacements, empty($recipient['lang']) || empty($modSettings['userLanguage']) ? $language : $recipient['lang']); |
||
| 2178 | |||
| 2179 | // Send off the email. |
||
| 2180 | sendmail($recipient['email'], $emaildata['subject'], $emaildata['body'], null, $template, $emaildata['is_html'], 1); |
||
| 2181 | } |
||
| 2182 | } |
||
| 2183 | |||
| 2184 | /* |
||
| 2185 | * Locates the most appropriate temp directory. |
||
| 2186 | * |
||
| 2187 | * Systems using `open_basedir` restrictions may receive errors with |
||
| 2188 | * `sys_get_temp_dir()` due to misconfigurations on servers. Other |
||
| 2189 | * cases sys_temp_dir may not be set to a safe value. Additionaly |
||
| 2190 | * `sys_get_temp_dir` may use a readonly directory. This attempts to |
||
| 2191 | * find a working temp directory that is accessible under the |
||
| 2192 | * restrictions and is writable to the web service account. |
||
| 2193 | * |
||
| 2194 | * Directories cheked against `open_basedir`: |
||
| 2195 | * |
||
| 2196 | * - `sys_get_temp_dir()` |
||
| 2197 | * - `upload_tmp_dir` |
||
| 2198 | * - `session.save_path` |
||
| 2199 | * - `$cachedir` |
||
| 2200 | * |
||
| 2201 | * @return string |
||
| 2202 | */ |
||
| 2203 | function sm_temp_dir() |
||
| 2204 | { |
||
| 2205 | global $cachedir; |
||
| 2206 | |||
| 2207 | static $temp_dir = null; |
||
| 2208 | |||
| 2209 | // Already did this. |
||
| 2210 | if (!empty($temp_dir)) |
||
| 2211 | return $temp_dir; |
||
| 2212 | |||
| 2213 | // Temp Directory options order. |
||
| 2214 | $temp_dir_options = array( |
||
| 2215 | 0 => 'sys_get_temp_dir', |
||
| 2216 | 1 => 'upload_tmp_dir', |
||
| 2217 | 2 => 'session.save_path', |
||
| 2218 | 3 => 'cachedir' |
||
| 2219 | ); |
||
| 2220 | |||
| 2221 | // Determine if we should detect a restriction and what restrictions that may be. |
||
| 2222 | $restriction = !empty(ini_get('open_basedir')) ? explode(':', ini_get('open_basedir')) : false; |
||
| 2223 | |||
| 2224 | // Prevent any errors as we search. |
||
| 2225 | $old_error_reporting = error_reporting(0); |
||
| 2226 | |||
| 2227 | // Search for a working temp directory. |
||
| 2228 | foreach ($temp_dir_options as $id_temp => $temp_option) |
||
| 2229 | { |
||
| 2230 | $possible_temp = sm_temp_dir_option($temp_option); |
||
| 2231 | |||
| 2232 | // Check if we have a restriction preventing this from working. |
||
| 2233 | if ($restriction) |
||
| 2234 | { |
||
| 2235 | foreach ($restriction as $dir) |
||
| 2236 | { |
||
| 2237 | if (strpos($possible_temp, $dir) !== false && is_writable($possible_temp)) |
||
| 2238 | { |
||
| 2239 | $temp_dir = $possible_temp; |
||
| 2240 | break; |
||
| 2241 | } |
||
| 2242 | } |
||
| 2243 | } |
||
| 2244 | // No restrictions, but need to check for writable status. |
||
| 2245 | elseif (is_writable($possible_temp)) |
||
| 2246 | { |
||
| 2247 | $temp_dir = $possible_temp; |
||
| 2248 | break; |
||
| 2249 | } |
||
| 2250 | } |
||
| 2251 | |||
| 2252 | // Fall back to sys_get_temp_dir even though it won't work, so we have something. |
||
| 2253 | if (empty($temp_dir)) |
||
| 2254 | $temp_dir = sm_temp_dir_option('default'); |
||
| 2255 | |||
| 2256 | // Fix the path. |
||
| 2257 | $temp_dir = substr($temp_dir, -1) === '/' ? $temp_dir : $temp_dir . '/'; |
||
| 2258 | |||
| 2259 | // Put things back. |
||
| 2260 | error_reporting($old_error_reporting); |
||
| 2261 | |||
| 2262 | return $temp_dir; |
||
| 2263 | } |
||
| 2264 | |||
| 2265 | /** |
||
| 2266 | * Internal function for sm_temp_dir. |
||
| 2267 | * |
||
| 2268 | * @param string $option Which temp_dir option to use |
||
| 2269 | */ |
||
| 2270 | function sm_temp_dir_option($option = 'sys_get_temp_dir') |
||
| 2271 | { |
||
| 2272 | global $cachedir; |
||
| 2273 | |||
| 2274 | if ($option === 'cachedir') |
||
| 2275 | return rtrim($cachedir, '/'); |
||
| 2276 | elseif ($option === 'session.save_path') |
||
| 2277 | return rtrim(ini_get('session.save_path'), '/'); |
||
| 2278 | elseif ($option === 'upload_tmp_dir') |
||
| 2279 | return rtrim(ini_get('upload_tmp_dir'), '/'); |
||
| 2280 | // This is the default option. |
||
| 2281 | else |
||
| 2282 | return sys_get_temp_dir(); |
||
| 2283 | } |
||
| 2284 | |||
| 2285 | ?> |