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