Subs-Admin.php 18 KB

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