|
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.0 |
|
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) {} |
|
|
|
|
|
|
1816
|
|
|
catch (ErrorException $e) {} |
|
|
|
|
|
|
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
|
|
|
?> |