comparison forum/Sources/Subs-Admin.php @ 76:e3e11437ecea website

Add forum code
author Chris Cannam
date Sun, 07 Jul 2013 11:25:48 +0200
parents
children
comparison
equal deleted inserted replaced
75:72f59aa7e503 76:e3e11437ecea
1 <?php
2
3 /**
4 * Simple Machines Forum (SMF)
5 *
6 * @package SMF
7 * @author Simple Machines http://www.simplemachines.org
8 * @copyright 2011 Simple Machines
9 * @license http://www.simplemachines.org/about/smf/license.php BSD
10 *
11 * @version 2.0
12 */
13
14 if (!defined('SMF'))
15 die('Hacking attempt...');
16
17 /* This file contains functions that are specifically done by administrators.
18 The most important function in this file for mod makers happens to be the
19 updateSettingsFile() function, but it shouldn't be used often anyway.
20
21 void getServerVersions(array checkFor)
22 - get a list of versions that are currently installed on the server.
23
24 void getFileVersions(array versionOptions)
25 - get detailed version information about the physical SMF files on the
26 server.
27 - the input parameter allows to set whether to include SSI.php and
28 whether the results should be sorted.
29 - returns an array containing information on source files, templates
30 and language files found in the default theme directory (grouped by
31 language).
32
33 void updateSettingsFile(array config_vars)
34 - updates the Settings.php file with the changes in config_vars.
35 - expects config_vars to be an associative array, with the keys as the
36 variable names in Settings.php, and the values the varaible values.
37 - does not escape or quote values.
38 - preserves case, formatting, and additional options in file.
39 - writes nothing if the resulting file would be less than 10 lines
40 in length (sanity check for read lock.)
41
42 void updateAdminPreferences()
43 - saves the admins current preferences to the database.
44
45 void emailAdmins(string $template, array $replacements = array(), additional_recipients = array())
46 - loads all users who are admins or have the admin forum permission.
47 - uses the email template and replacements passed in the parameters.
48 - sends them an email.
49
50 bool updateLastDatabaseError()
51 - attempts to use the backup file first, to store the last database error
52 - and only update Settings.php if the first was successful.
53
54 */
55
56 function getServerVersions($checkFor)
57 {
58 global $txt, $db_connection, $_PHPA, $smcFunc, $memcached, $modSettings;
59
60 loadLanguage('Admin');
61
62 $versions = array();
63
64 // Is GD available? If it is, we should show version information for it too.
65 if (in_array('gd', $checkFor) && function_exists('gd_info'))
66 {
67 $temp = gd_info();
68 $versions['gd'] = array('title' => $txt['support_versions_gd'], 'version' => $temp['GD Version']);
69 }
70
71 // Now lets check for the Database.
72 if (in_array('db_server', $checkFor))
73 {
74 db_extend();
75 if (!isset($db_connection) || $db_connection === false)
76 trigger_error('getServerVersions(): you need to be connected to the database in order to get its server version', E_USER_NOTICE);
77 else
78 {
79 $versions['db_server'] = array('title' => sprintf($txt['support_versions_db'], $smcFunc['db_title']), 'version' => '');
80 $versions['db_server']['version'] = $smcFunc['db_get_version']();
81 }
82 }
83
84 // If we're using memcache we need the server info.
85 if (empty($memcached) && function_exists('memcache_get') && isset($modSettings['cache_memcached']) && trim($modSettings['cache_memcached']) != '')
86 get_memcached_server();
87
88 // Check to see if we have any accelerators installed...
89 if (in_array('mmcache', $checkFor) && defined('MMCACHE_VERSION'))
90 $versions['mmcache'] = array('title' => 'Turck MMCache', 'version' => MMCACHE_VERSION);
91 if (in_array('eaccelerator', $checkFor) && defined('EACCELERATOR_VERSION'))
92 $versions['eaccelerator'] = array('title' => 'eAccelerator', 'version' => EACCELERATOR_VERSION);
93 if (in_array('phpa', $checkFor) && isset($_PHPA))
94 $versions['phpa'] = array('title' => 'ionCube PHP-Accelerator', 'version' => $_PHPA['VERSION']);
95 if (in_array('apc', $checkFor) && extension_loaded('apc'))
96 $versions['apc'] = array('title' => 'Alternative PHP Cache', 'version' => phpversion('apc'));
97 if (in_array('memcache', $checkFor) && function_exists('memcache_set'))
98 $versions['memcache'] = array('title' => 'Memcached', 'version' => empty($memcached) ? '???' : memcache_get_version($memcached));
99 if (in_array('xcache', $checkFor) && function_exists('xcache_set'))
100 $versions['xcache'] = array('title' => 'XCache', 'version' => XCACHE_VERSION);
101 if (in_array('php', $checkFor))
102 $versions['php'] = array('title' => 'PHP', 'version' => PHP_VERSION);
103
104 if (in_array('server', $checkFor))
105 $versions['server'] = array('title' => $txt['support_versions_server'], 'version' => $_SERVER['SERVER_SOFTWARE']);
106
107 return $versions;
108 }
109
110 // Search through source, theme and language files to determine their version.
111 function getFileVersions(&$versionOptions)
112 {
113 global $boarddir, $sourcedir, $settings;
114
115 // Default place to find the languages would be the default theme dir.
116 $lang_dir = $settings['default_theme_dir'] . '/languages';
117
118 $version_info = array(
119 'file_versions' => array(),
120 'default_template_versions' => array(),
121 'template_versions' => array(),
122 'default_language_versions' => array(),
123 );
124
125 // Find the version in SSI.php's file header.
126 if (!empty($versionOptions['include_ssi']) && file_exists($boarddir . '/SSI.php'))
127 {
128 $fp = fopen($boarddir . '/SSI.php', 'rb');
129 $header = fread($fp, 4096);
130 fclose($fp);
131
132 // The comment looks rougly like... that.
133 if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
134 $version_info['file_versions']['SSI.php'] = $match[1];
135 // Not found! This is bad.
136 else
137 $version_info['file_versions']['SSI.php'] = '??';
138 }
139
140 // Do the paid subscriptions handler?
141 if (!empty($versionOptions['include_subscriptions']) && file_exists($boarddir . '/subscriptions.php'))
142 {
143 $fp = fopen($boarddir . '/subscriptions.php', 'rb');
144 $header = fread($fp, 4096);
145 fclose($fp);
146
147 // Found it?
148 if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
149 $version_info['file_versions']['subscriptions.php'] = $match[1];
150 // If we haven't how do we all get paid?
151 else
152 $version_info['file_versions']['subscriptions.php'] = '??';
153 }
154
155 // Load all the files in the Sources directory, except this file and the redirect.
156 $sources_dir = dir($sourcedir);
157 while ($entry = $sources_dir->read())
158 {
159 if (substr($entry, -4) === '.php' && !is_dir($sourcedir . '/' . $entry) && $entry !== 'index.php')
160 {
161 // Read the first 4k from the file.... enough for the header.
162 $fp = fopen($sourcedir . '/' . $entry, 'rb');
163 $header = fread($fp, 4096);
164 fclose($fp);
165
166 // Look for the version comment in the file header.
167 if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
168 $version_info['file_versions'][$entry] = $match[1];
169 // It wasn't found, but the file was... show a '??'.
170 else
171 $version_info['file_versions'][$entry] = '??';
172 }
173 }
174 $sources_dir->close();
175
176 // Load all the files in the default template directory - and the current theme if applicable.
177 $directories = array('default_template_versions' => $settings['default_theme_dir']);
178 if ($settings['theme_id'] != 1)
179 $directories += array('template_versions' => $settings['theme_dir']);
180
181 foreach ($directories as $type => $dirname)
182 {
183 $this_dir = dir($dirname);
184 while ($entry = $this_dir->read())
185 {
186 if (substr($entry, -12) == 'template.php' && !is_dir($dirname . '/' . $entry))
187 {
188 // Read the first 768 bytes from the file.... enough for the header.
189 $fp = fopen($dirname . '/' . $entry, 'rb');
190 $header = fread($fp, 768);
191 fclose($fp);
192
193 // Look for the version comment in the file header.
194 if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
195 $version_info[$type][$entry] = $match[1];
196 // It wasn't found, but the file was... show a '??'.
197 else
198 $version_info[$type][$entry] = '??';
199 }
200 }
201 $this_dir->close();
202 }
203
204 // Load up all the files in the default language directory and sort by language.
205 $this_dir = dir($lang_dir);
206 while ($entry = $this_dir->read())
207 {
208 if (substr($entry, -4) == '.php' && $entry != 'index.php' && !is_dir($lang_dir . '/' . $entry))
209 {
210 // Read the first 768 bytes from the file.... enough for the header.
211 $fp = fopen($lang_dir . '/' . $entry, 'rb');
212 $header = fread($fp, 768);
213 fclose($fp);
214
215 // Split the file name off into useful bits.
216 list ($name, $language) = explode('.', $entry);
217
218 // Look for the version comment in the file header.
219 if (preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '(?:[\s]{2}|\*/)~i', $header, $match) == 1)
220 $version_info['default_language_versions'][$language][$name] = $match[1];
221 // It wasn't found, but the file was... show a '??'.
222 else
223 $version_info['default_language_versions'][$language][$name] = '??';
224 }
225 }
226 $this_dir->close();
227
228 // Sort the file versions by filename.
229 if (!empty($versionOptions['sort_results']))
230 {
231 ksort($version_info['file_versions']);
232 ksort($version_info['default_template_versions']);
233 ksort($version_info['template_versions']);
234 ksort($version_info['default_language_versions']);
235
236 // For languages sort each language too.
237 foreach ($version_info['default_language_versions'] as $language => $dummy)
238 ksort($version_info['default_language_versions'][$language]);
239 }
240 return $version_info;
241 }
242
243 // Update the Settings.php file.
244 function updateSettingsFile($config_vars)
245 {
246 global $boarddir, $cachedir;
247
248 // When is Settings.php last changed?
249 $last_settings_change = filemtime($boarddir . '/Settings.php');
250
251 // Load the file. Break it up based on \r or \n, and then clean out extra characters.
252 $settingsArray = trim(file_get_contents($boarddir . '/Settings.php'));
253 if (strpos($settingsArray, "\n") !== false)
254 $settingsArray = explode("\n", $settingsArray);
255 elseif (strpos($settingsArray, "\r") !== false)
256 $settingsArray = explode("\r", $settingsArray);
257 else
258 return;
259
260 // Make sure we got a good file.
261 if (count($config_vars) == 1 && isset($config_vars['db_last_error']))
262 {
263 $temp = trim(implode("\n", $settingsArray));
264 if (substr($temp, 0, 5) != '<?php' || substr($temp, -2) != '?' . '>')
265 return;
266 if (strpos($temp, 'sourcedir') === false || strpos($temp, 'boarddir') === false || strpos($temp, 'cookiename') === false)
267 return;
268 }
269
270 // Presumably, the file has to have stuff in it for this function to be called :P.
271 if (count($settingsArray) < 10)
272 return;
273
274 foreach ($settingsArray as $k => $dummy)
275 $settingsArray[$k] = strtr($dummy, array("\r" => '')) . "\n";
276
277 for ($i = 0, $n = count($settingsArray); $i < $n; $i++)
278 {
279 // Don't trim or bother with it if it's not a variable.
280 if (substr($settingsArray[$i], 0, 1) != '$')
281 continue;
282
283 $settingsArray[$i] = trim($settingsArray[$i]) . "\n";
284
285 // Look through the variables to set....
286 foreach ($config_vars as $var => $val)
287 {
288 if (strncasecmp($settingsArray[$i], '$' . $var, 1 + strlen($var)) == 0)
289 {
290 $comment = strstr(substr($settingsArray[$i], strpos($settingsArray[$i], ';')), '#');
291 $settingsArray[$i] = '$' . $var . ' = ' . $val . ';' . ($comment == '' ? '' : "\t\t" . rtrim($comment)) . "\n";
292
293 // This one's been 'used', so to speak.
294 unset($config_vars[$var]);
295 }
296 }
297
298 if (substr(trim($settingsArray[$i]), 0, 2) == '?' . '>')
299 $end = $i;
300 }
301
302 // This should never happen, but apparently it is happening.
303 if (empty($end) || $end < 10)
304 $end = count($settingsArray) - 1;
305
306 // Still more? Add them at the end.
307 if (!empty($config_vars))
308 {
309 if (trim($settingsArray[$end]) == '?' . '>')
310 $settingsArray[$end++] = '';
311 else
312 $end++;
313
314 foreach ($config_vars as $var => $val)
315 $settingsArray[$end++] = '$' . $var . ' = ' . $val . ';' . "\n";
316 $settingsArray[$end] = '?' . '>';
317 }
318 else
319 $settingsArray[$end] = trim($settingsArray[$end]);
320
321 // Sanity error checking: the file needs to be at least 12 lines.
322 if (count($settingsArray) < 12)
323 return;
324
325 // Try to avoid a few pitfalls:
326 // like a possible race condition,
327 // or a failure to write at low diskspace
328
329 // Check before you act: if cache is enabled, we can do a simple test
330 // Can we even write things on this filesystem?
331 if ((empty($cachedir) || !file_exists($cachedir)) && file_exists($boarddir . '/cache'))
332 $cachedir = $boarddir . '/cache';
333 $test_fp = @fopen($cachedir . '/settings_update.tmp', "w+");
334 if ($test_fp)
335 {
336 fclose($test_fp);
337
338 $test_fp = @fopen($cachedir . '/settings_update.tmp', 'r+');
339 $written_bytes = fwrite($test_fp, "test");
340 fclose($test_fp);
341 @unlink($cachedir . '/settings_update.tmp');
342
343 if ($written_bytes !== strlen("test"))
344 {
345 // Oops. Low disk space, perhaps. Don't mess with Settings.php then.
346 // No means no. :P
347 return;
348 }
349 }
350
351 // Protect me from what I want! :P
352 clearstatcache();
353 if (filemtime($boarddir . '/Settings.php') === $last_settings_change)
354 {
355 // You asked for it...
356 // Blank out the file - done to fix a oddity with some servers.
357 $fp = @fopen($boarddir . '/Settings.php', 'w');
358
359 // Is it even writable, though?
360 if ($fp)
361 {
362 fclose($fp);
363
364 $fp = fopen($boarddir . '/Settings.php', 'r+');
365 foreach ($settingsArray as $line)
366 fwrite($fp, strtr($line, "\r", ''));
367 fclose($fp);
368 }
369 }
370 }
371
372 function updateAdminPreferences()
373 {
374 global $options, $context, $smcFunc, $settings, $user_info;
375
376 // This must exist!
377 if (!isset($context['admin_preferences']))
378 return false;
379
380 // This is what we'll be saving.
381 $options['admin_preferences'] = serialize($context['admin_preferences']);
382
383 // Just check we haven't ended up with something theme exclusive somehow.
384 $smcFunc['db_query']('', '
385 DELETE FROM {db_prefix}themes
386 WHERE id_theme != {int:default_theme}
387 AND variable = {string:admin_preferences}',
388 array(
389 'default_theme' => 1,
390 'admin_preferences' => 'admin_preferences',
391 )
392 );
393
394 // Update the themes table.
395 $smcFunc['db_insert']('replace',
396 '{db_prefix}themes',
397 array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
398 array($user_info['id'], 1, 'admin_preferences', $options['admin_preferences']),
399 array('id_member', 'id_theme', 'variable')
400 );
401
402 // Make sure we invalidate any cache.
403 cache_put_data('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id'], null, 0);
404 }
405
406 // Send all the administrators a lovely email.
407 function emailAdmins($template, $replacements = array(), $additional_recipients = array())
408 {
409 global $smcFunc, $sourcedir, $language, $modSettings;
410
411 // We certainly want this.
412 require_once($sourcedir . '/Subs-Post.php');
413
414 // Load all groups which are effectively admins.
415 $request = $smcFunc['db_query']('', '
416 SELECT id_group
417 FROM {db_prefix}permissions
418 WHERE permission = {string:admin_forum}
419 AND add_deny = {int:add_deny}
420 AND id_group != {int:id_group}',
421 array(
422 'add_deny' => 1,
423 'id_group' => 0,
424 'admin_forum' => 'admin_forum',
425 )
426 );
427 $groups = array(1);
428 while ($row = $smcFunc['db_fetch_assoc']($request))
429 $groups[] = $row['id_group'];
430 $smcFunc['db_free_result']($request);
431
432 $request = $smcFunc['db_query']('', '
433 SELECT id_member, member_name, real_name, lngfile, email_address
434 FROM {db_prefix}members
435 WHERE (id_group IN ({array_int:group_list}) OR FIND_IN_SET({raw:group_array_implode}, additional_groups) != 0)
436 AND notify_types != {int:notify_types}
437 ORDER BY lngfile',
438 array(
439 'group_list' => $groups,
440 'notify_types' => 4,
441 'group_array_implode' => implode(', additional_groups) != 0 OR FIND_IN_SET(', $groups),
442 )
443 );
444 $emails_sent = array();
445 while ($row = $smcFunc['db_fetch_assoc']($request))
446 {
447 // Stick their particulars in the replacement data.
448 $replacements['IDMEMBER'] = $row['id_member'];
449 $replacements['REALNAME'] = $row['member_name'];
450 $replacements['USERNAME'] = $row['real_name'];
451
452 // Load the data from the template.
453 $emaildata = loadEmailTemplate($template, $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']);
454
455 // Then send the actual email.
456 sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 1);
457
458 // Track who we emailed so we don't do it twice.
459 $emails_sent[] = $row['email_address'];
460 }
461 $smcFunc['db_free_result']($request);
462
463 // Any additional users we must email this to?
464 if (!empty($additional_recipients))
465 foreach ($additional_recipients as $recipient)
466 {
467 if (in_array($recipient['email'], $emails_sent))
468 continue;
469
470 $replacements['IDMEMBER'] = $recipient['id'];
471 $replacements['REALNAME'] = $recipient['name'];
472 $replacements['USERNAME'] = $recipient['name'];
473
474 // Load the template again.
475 $emaildata = loadEmailTemplate($template, $replacements, empty($recipient['lang']) || empty($modSettings['userLanguage']) ? $language : $recipient['lang']);
476
477 // Send off the email.
478 sendmail($recipient['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 1);
479 }
480 }
481
482 function updateLastDatabaseError()
483 {
484 global $boarddir;
485
486 // Find out this way if we can even write things on this filesystem.
487 // In addition, store things first in the backup file
488
489 $last_settings_change = @filemtime($boarddir . '/Settings.php');
490
491 // Make sure the backup file is there...
492 $file = $boarddir . '/Settings_bak.php';
493 if ((!file_exists($file) || filesize($file) == 0) && !copy($boarddir . '/Settings.php', $file))
494 return false;
495
496 // ...and writable!
497 if (!is_writable($file))
498 {
499 chmod($file, 0755);
500 if (!is_writable($file))
501 {
502 chmod($file, 0775);
503 if (!is_writable($file))
504 {
505 chmod($file, 0777);
506 if (!is_writable($file))
507 return false;
508 }
509 }
510 }
511
512 // Put the new timestamp.
513 $data = file_get_contents($file);
514 $data = preg_replace('~\$db_last_error = \d+;~', '$db_last_error = ' . time() . ';', $data);
515
516 // Open the backup file for writing
517 if ($fp = @fopen($file, 'w'))
518 {
519 // Reset the file buffer.
520 set_file_buffer($fp, 0);
521
522 // Update the file.
523 $t = flock($fp, LOCK_EX);
524 $bytes = fwrite($fp, $data);
525 flock($fp, LOCK_UN);
526 fclose($fp);
527
528 // Was it a success?
529 // ...only relevant if we're still dealing with the same good ole' settings file.
530 clearstatcache();
531 if (($bytes == strlen($data)) && (filemtime($boarddir . '/Settings.php') === $last_settings_change))
532 {
533 // This is our new Settings file...
534 // At least this one is an atomic operation
535 @copy($file, $boarddir . '/Settings.php');
536 return true;
537 }
538 else
539 {
540 // Oops. Someone might have been faster
541 // or we have no more disk space left, troubles, troubles...
542 // Copy the file back and run for your life!
543 @copy($boarddir . '/Settings.php', $file);
544 }
545 }
546
547 return false;
548 }
549
550 ?>