ScheduledTasks.php 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779
  1. <?php
  2. /**
  3. * This file is automatically called and handles all manner of scheduled things.
  4. *
  5. * Simple Machines Forum (SMF)
  6. *
  7. * @package SMF
  8. * @author Simple Machines http://www.simplemachines.org
  9. * @copyright 2013 Simple Machines and individual contributors
  10. * @license http://www.simplemachines.org/about/smf/license.php BSD
  11. *
  12. * @version 2.1 Alpha 1
  13. */
  14. if (!defined('SMF'))
  15. die('No direct access...');
  16. /**
  17. * This function works out what to do!
  18. */
  19. function AutoTask()
  20. {
  21. global $time_start, $modSettings, $smcFunc;
  22. // Special case for doing the mail queue.
  23. if (isset($_GET['scheduled']) && $_GET['scheduled'] == 'mailq')
  24. ReduceMailQueue();
  25. else
  26. {
  27. call_integration_hook('integrate_autotask_include');
  28. // Select the next task to do.
  29. $request = $smcFunc['db_query']('', '
  30. SELECT id_task, task, next_time, time_offset, time_regularity, time_unit
  31. FROM {db_prefix}scheduled_tasks
  32. WHERE disabled = {int:not_disabled}
  33. AND next_time <= {int:current_time}
  34. ORDER BY next_time ASC
  35. LIMIT 1',
  36. array(
  37. 'not_disabled' => 0,
  38. 'current_time' => time(),
  39. )
  40. );
  41. if ($smcFunc['db_num_rows']($request) != 0)
  42. {
  43. // The two important things really...
  44. $row = $smcFunc['db_fetch_assoc']($request);
  45. // When should this next be run?
  46. $next_time = next_time($row['time_regularity'], $row['time_unit'], $row['time_offset']);
  47. // How long in seconds it the gap?
  48. $duration = $row['time_regularity'];
  49. if ($row['time_unit'] == 'm')
  50. $duration *= 60;
  51. elseif ($row['time_unit'] == 'h')
  52. $duration *= 3600;
  53. elseif ($row['time_unit'] == 'd')
  54. $duration *= 86400;
  55. elseif ($row['time_unit'] == 'w')
  56. $duration *= 604800;
  57. // If we were really late running this task actually skip the next one.
  58. if (time() + ($duration / 2) > $next_time)
  59. $next_time += $duration;
  60. // Update it now, so no others run this!
  61. $smcFunc['db_query']('', '
  62. UPDATE {db_prefix}scheduled_tasks
  63. SET next_time = {int:next_time}
  64. WHERE id_task = {int:id_task}
  65. AND next_time = {int:current_next_time}',
  66. array(
  67. 'next_time' => $next_time,
  68. 'id_task' => $row['id_task'],
  69. 'current_next_time' => $row['next_time'],
  70. )
  71. );
  72. $affected_rows = $smcFunc['db_affected_rows']();
  73. // The function must exist or we are wasting our time, plus do some timestamp checking, and database check!
  74. if (function_exists('scheduled_' . $row['task']) && (!isset($_GET['ts']) || $_GET['ts'] == $row['next_time']) && $affected_rows)
  75. {
  76. ignore_user_abort(true);
  77. // Do the task...
  78. $completed = call_user_func('scheduled_' . $row['task']);
  79. // Log that we did it ;)
  80. if ($completed)
  81. {
  82. $total_time = round(array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)), 3);
  83. $smcFunc['db_insert']('',
  84. '{db_prefix}log_scheduled_tasks',
  85. array(
  86. 'id_task' => 'int', 'time_run' => 'int', 'time_taken' => 'float',
  87. ),
  88. array(
  89. $row['id_task'], time(), (int) $total_time,
  90. ),
  91. array()
  92. );
  93. }
  94. }
  95. }
  96. $smcFunc['db_free_result']($request);
  97. // Get the next timestamp right.
  98. $request = $smcFunc['db_query']('', '
  99. SELECT next_time
  100. FROM {db_prefix}scheduled_tasks
  101. WHERE disabled = {int:not_disabled}
  102. ORDER BY next_time ASC
  103. LIMIT 1',
  104. array(
  105. 'not_disabled' => 0,
  106. )
  107. );
  108. // No new task scheduled yet?
  109. if ($smcFunc['db_num_rows']($request) === 0)
  110. $nextEvent = time() + 86400;
  111. else
  112. list ($nextEvent) = $smcFunc['db_fetch_row']($request);
  113. $smcFunc['db_free_result']($request);
  114. updateSettings(array('next_task_time' => $nextEvent));
  115. }
  116. // Shall we return?
  117. if (!isset($_GET['scheduled']))
  118. return true;
  119. // Finally, send some stuff...
  120. header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
  121. header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
  122. header('Content-Type: image/gif');
  123. die("\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B");
  124. }
  125. /**
  126. * Function to sending out approval notices to moderators etc.
  127. */
  128. function scheduled_approval_notification()
  129. {
  130. global $scripturl, $modSettings, $mbname, $txt, $sourcedir, $smcFunc;
  131. // Grab all the items awaiting approval and sort type then board - clear up any things that are no longer relevant.
  132. $request = $smcFunc['db_query']('', '
  133. SELECT aq.id_msg, aq.id_attach, aq.id_event, m.id_topic, m.id_board, m.subject, t.id_first_msg,
  134. b.id_profile
  135. FROM {db_prefix}approval_queue AS aq
  136. INNER JOIN {db_prefix}messages AS m ON (m.id_msg = aq.id_msg)
  137. INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
  138. INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)',
  139. array(
  140. )
  141. );
  142. $notices = array();
  143. $profiles = array();
  144. while ($row = $smcFunc['db_fetch_assoc']($request))
  145. {
  146. // If this is no longer around we'll ignore it.
  147. if (empty($row['id_topic']))
  148. continue;
  149. // What type is it?
  150. if ($row['id_first_msg'] && $row['id_first_msg'] == $row['id_msg'])
  151. $type = 'topic';
  152. elseif ($row['id_attach'])
  153. $type = 'attach';
  154. else
  155. $type = 'msg';
  156. // Add it to the array otherwise.
  157. $notices[$row['id_board']][$type][] = array(
  158. 'subject' => $row['subject'],
  159. 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
  160. );
  161. // Store the profile for a bit later.
  162. $profiles[$row['id_board']] = $row['id_profile'];
  163. }
  164. $smcFunc['db_free_result']($request);
  165. // Delete it all!
  166. $smcFunc['db_query']('', '
  167. DELETE FROM {db_prefix}approval_queue',
  168. array(
  169. )
  170. );
  171. // If nothing quit now.
  172. if (empty($notices))
  173. return true;
  174. // Now we need to think about finding out *who* can approve - this is hard!
  175. // First off, get all the groups with this permission and sort by board.
  176. $request = $smcFunc['db_query']('', '
  177. SELECT id_group, id_profile, add_deny
  178. FROM {db_prefix}board_permissions
  179. WHERE permission = {string:approve_posts}
  180. AND id_profile IN ({array_int:profile_list})',
  181. array(
  182. 'profile_list' => $profiles,
  183. 'approve_posts' => 'approve_posts',
  184. )
  185. );
  186. $perms = array();
  187. $addGroups = array(1);
  188. while ($row = $smcFunc['db_fetch_assoc']($request))
  189. {
  190. // Sorry guys, but we have to ignore guests AND members - it would be too many otherwise.
  191. if ($row['id_group'] < 2)
  192. continue;
  193. $perms[$row['id_profile']][$row['add_deny'] ? 'add' : 'deny'][] = $row['id_group'];
  194. // Anyone who can access has to be considered.
  195. if ($row['add_deny'])
  196. $addGroups[] = $row['id_group'];
  197. }
  198. $smcFunc['db_free_result']($request);
  199. // Grab the moderators if they have permission!
  200. $mods = array();
  201. $members = array();
  202. if (in_array(2, $addGroups))
  203. {
  204. $request = $smcFunc['db_query']('', '
  205. SELECT id_member, id_board
  206. FROM {db_prefix}moderators',
  207. array(
  208. )
  209. );
  210. while ($row = $smcFunc['db_fetch_assoc']($request))
  211. {
  212. $mods[$row['id_member']][$row['id_board']] = true;
  213. // Make sure they get included in the big loop.
  214. $members[] = $row['id_member'];
  215. }
  216. $smcFunc['db_free_result']($request);
  217. }
  218. // Come along one and all... until we reject you ;)
  219. $request = $smcFunc['db_query']('', '
  220. SELECT id_member, real_name, email_address, lngfile, id_group, additional_groups, mod_prefs
  221. FROM {db_prefix}members
  222. WHERE id_group IN ({array_int:additional_group_list})
  223. OR FIND_IN_SET({raw:additional_group_list_implode}, additional_groups) != 0' . (empty($members) ? '' : '
  224. OR id_member IN ({array_int:member_list})') . '
  225. ORDER BY lngfile',
  226. array(
  227. 'additional_group_list' => $addGroups,
  228. 'member_list' => $members,
  229. 'additional_group_list_implode' => implode(', additional_groups) != 0 OR FIND_IN_SET(', $addGroups),
  230. )
  231. );
  232. $members = array();
  233. while ($row = $smcFunc['db_fetch_assoc']($request))
  234. {
  235. // Check whether they are interested.
  236. if (!empty($row['mod_prefs']))
  237. {
  238. list(,, $pref_binary) = explode('|', $row['mod_prefs']);
  239. if (!($pref_binary & 4))
  240. continue;
  241. }
  242. $members[$row['id_member']] = array(
  243. 'id' => $row['id_member'],
  244. 'groups' => array_merge(explode(',', $row['additional_groups']), array($row['id_group'])),
  245. 'language' => $row['lngfile'],
  246. 'email' => $row['email_address'],
  247. 'name' => $row['real_name'],
  248. );
  249. }
  250. $smcFunc['db_free_result']($request);
  251. // Get the mailing stuff.
  252. require_once($sourcedir . '/Subs-Post.php');
  253. // Need the below for loadLanguage to work!
  254. loadEssentialThemeData();
  255. $current_language = '';
  256. // Finally, loop through each member, work out what they can do, and send it.
  257. foreach ($members as $id => $member)
  258. {
  259. $emailbody = '';
  260. // Load the language file as required.
  261. if (empty($current_language) || $current_language != $member['language'])
  262. $current_language = loadLanguage('EmailTemplates', $member['language'], false);
  263. // Loop through each notice...
  264. foreach ($notices as $board => $notice)
  265. {
  266. $access = false;
  267. // Can they mod in this board?
  268. if (isset($mods[$id][$board]))
  269. $access = true;
  270. // Do the group check...
  271. if (!$access && isset($perms[$profiles[$board]]['add']))
  272. {
  273. // They can access?!
  274. if (array_intersect($perms[$profiles[$board]]['add'], $member['groups']))
  275. $access = true;
  276. // If they have deny rights don't consider them!
  277. if (isset($perms[$profiles[$board]]['deny']))
  278. if (array_intersect($perms[$profiles[$board]]['deny'], $member['groups']))
  279. $access = false;
  280. }
  281. // Finally, fix it for admins!
  282. if (in_array(1, $member['groups']))
  283. $access = true;
  284. // If they can't access it then give it a break!
  285. if (!$access)
  286. continue;
  287. foreach ($notice as $type => $items)
  288. {
  289. // Build up the top of this section.
  290. $emailbody .= $txt['scheduled_approval_email_' . $type] . "\n" .
  291. '------------------------------------------------------' . "\n";
  292. foreach ($items as $item)
  293. $emailbody .= $item['subject'] . ' - ' . $item['href'] . "\n";
  294. $emailbody .= "\n";
  295. }
  296. }
  297. if ($emailbody == '')
  298. continue;
  299. $replacements = array(
  300. 'REALNAME' => $member['name'],
  301. 'BODY' => $emailbody,
  302. );
  303. $emaildata = loadEmailTemplate('scheduled_approval', $replacements, $current_language);
  304. // Send the actual email.
  305. sendmail($member['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 2);
  306. }
  307. // All went well!
  308. return true;
  309. }
  310. /**
  311. * Do some daily cleaning up.
  312. */
  313. function scheduled_daily_maintenance()
  314. {
  315. global $smcFunc, $modSettings, $sourcedir, $db_type;
  316. // First clean out the cache.
  317. clean_cache();
  318. // If warning decrement is enabled and we have people who have not had a new warning in 24 hours, lower their warning level.
  319. list (, , $modSettings['warning_decrement']) = explode(',', $modSettings['warning_settings']);
  320. if ($modSettings['warning_decrement'])
  321. {
  322. // Find every member who has a warning level...
  323. $request = $smcFunc['db_query']('', '
  324. SELECT id_member, warning
  325. FROM {db_prefix}members
  326. WHERE warning > {int:no_warning}',
  327. array(
  328. 'no_warning' => 0,
  329. )
  330. );
  331. $members = array();
  332. while ($row = $smcFunc['db_fetch_assoc']($request))
  333. $members[$row['id_member']] = $row['warning'];
  334. $smcFunc['db_free_result']($request);
  335. // Have some members to check?
  336. if (!empty($members))
  337. {
  338. // Find out when they were last warned.
  339. $request = $smcFunc['db_query']('', '
  340. SELECT id_recipient, MAX(log_time) AS last_warning
  341. FROM {db_prefix}log_comments
  342. WHERE id_recipient IN ({array_int:member_list})
  343. AND comment_type = {string:warning}
  344. GROUP BY id_recipient',
  345. array(
  346. 'member_list' => array_keys($members),
  347. 'warning' => 'warning',
  348. )
  349. );
  350. $member_changes = array();
  351. while ($row = $smcFunc['db_fetch_assoc']($request))
  352. {
  353. // More than 24 hours ago?
  354. if ($row['last_warning'] <= time() - 86400)
  355. $member_changes[] = array(
  356. 'id' => $row['id_recipient'],
  357. 'warning' => $members[$row['id_recipient']] >= $modSettings['warning_decrement'] ? $members[$row['id_recipient']] - $modSettings['warning_decrement'] : 0,
  358. );
  359. }
  360. $smcFunc['db_free_result']($request);
  361. // Have some members to change?
  362. if (!empty($member_changes))
  363. foreach ($member_changes as $change)
  364. $smcFunc['db_query']('', '
  365. UPDATE {db_prefix}members
  366. SET warning = {int:warning}
  367. WHERE id_member = {int:id_member}',
  368. array(
  369. 'warning' => $change['warning'],
  370. 'id_member' => $change['id'],
  371. )
  372. );
  373. }
  374. }
  375. // Do any spider stuff.
  376. if (!empty($modSettings['spider_mode']) && $modSettings['spider_mode'] > 1)
  377. {
  378. require_once($sourcedir . '/ManageSearchEngines.php');
  379. consolidateSpiderStats();
  380. }
  381. // Check the database version - for some buggy MySQL version.
  382. $server_version = $smcFunc['db_server_info']();
  383. if (($db_type == 'mysql' || $db_type == 'mysqli') && in_array(substr($server_version, 0, 6), array('5.0.50', '5.0.51')))
  384. updateSettings(array('db_mysql_group_by_fix' => '1'));
  385. elseif (!empty($modSettings['db_mysql_group_by_fix']))
  386. $smcFunc['db_query']('', '
  387. DELETE FROM {db_prefix}settings
  388. WHERE variable = {string:mysql_fix}',
  389. array(
  390. 'mysql_fix' => 'db_mysql_group_by_fix',
  391. )
  392. );
  393. // Regenerate the Diffie-Hellman keys if OpenID is enabled.
  394. if (!empty($modSettings['enableOpenID']))
  395. {
  396. require_once($sourcedir . '/Subs-OpenID.php');
  397. smf_openID_setup_DH(true);
  398. }
  399. elseif (!empty($modSettings['dh_keys']))
  400. $smcFunc['db_query']('', '
  401. DELETE FROM {db_prefix}settings
  402. WHERE variable = {string:dh_keys}',
  403. array(
  404. 'dh_keys' => 'dh_keys',
  405. )
  406. );
  407. // Clean up some old login history information.
  408. $smcFunc['db_query']('', '
  409. DELETE FROM {db_prefix}member_logins
  410. WHERE time > {int:oldLogins}',
  411. array(
  412. 'oldLogins' => !empty($modSettings['loginHistoryDays']) ? 60 * 60 * $modSettings['loginHistoryDays'] : 108000,
  413. ));
  414. // Log we've done it...
  415. return true;
  416. }
  417. /**
  418. * Auto optimize the database?
  419. */
  420. function scheduled_auto_optimize()
  421. {
  422. global $modSettings, $smcFunc, $db_prefix, $db_type;
  423. // By default do it now!
  424. $delay = false;
  425. // As a kind of hack, if the server load is too great delay, but only by a bit!
  426. if (!empty($modSettings['load_average']) && !empty($modSettings['loadavg_auto_opt']) && $modSettings['load_average'] >= $modSettings['loadavg_auto_opt'])
  427. $delay = true;
  428. // Otherwise are we restricting the number of people online for this?
  429. if (!empty($modSettings['autoOptMaxOnline']))
  430. {
  431. $request = $smcFunc['db_query']('', '
  432. SELECT COUNT(*)
  433. FROM {db_prefix}log_online',
  434. array(
  435. )
  436. );
  437. list ($dont_do_it) = $smcFunc['db_fetch_row']($request);
  438. $smcFunc['db_free_result']($request);
  439. if ($dont_do_it > $modSettings['autoOptMaxOnline'])
  440. $delay = true;
  441. }
  442. // If we are gonna delay, do so now!
  443. if ($delay)
  444. return false;
  445. db_extend();
  446. // Get all the tables.
  447. $tables = $smcFunc['db_list_tables'](false, $db_prefix . '%');
  448. // Actually do the optimisation.
  449. if ($db_type == 'sqlite')
  450. $smcFunc['db_optimize_table']($tables[0]);
  451. else
  452. foreach ($tables as $table)
  453. $smcFunc['db_optimize_table']($table);
  454. // Return for the log...
  455. return true;
  456. }
  457. /**
  458. * Send out a daily email of all subscribed topics.
  459. */
  460. function scheduled_daily_digest()
  461. {
  462. global $is_weekly, $txt, $mbname, $scripturl, $sourcedir, $smcFunc, $context, $modSettings;
  463. // We'll want this...
  464. require_once($sourcedir . '/Subs-Post.php');
  465. loadEssentialThemeData();
  466. $is_weekly = !empty($is_weekly) ? 1 : 0;
  467. // Right - get all the notification data FIRST.
  468. $request = $smcFunc['db_query']('', '
  469. SELECT ln.id_topic, COALESCE(t.id_board, ln.id_board) AS id_board, mem.email_address, mem.member_name, mem.notify_types,
  470. mem.lngfile, mem.id_member
  471. FROM {db_prefix}log_notify AS ln
  472. INNER JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member)
  473. LEFT JOIN {db_prefix}topics AS t ON (ln.id_topic != {int:empty_topic} AND t.id_topic = ln.id_topic)
  474. WHERE mem.notify_regularity = {int:notify_regularity}
  475. AND mem.is_activated = {int:is_activated}',
  476. array(
  477. 'empty_topic' => 0,
  478. 'notify_regularity' => $is_weekly ? '3' : '2',
  479. 'is_activated' => 1,
  480. )
  481. );
  482. $members = array();
  483. $langs = array();
  484. $notify = array();
  485. while ($row = $smcFunc['db_fetch_assoc']($request))
  486. {
  487. if (!isset($members[$row['id_member']]))
  488. {
  489. $members[$row['id_member']] = array(
  490. 'email' => $row['email_address'],
  491. 'name' => $row['member_name'],
  492. 'id' => $row['id_member'],
  493. 'notifyMod' => $row['notify_types'] < 3 ? true : false,
  494. 'lang' => $row['lngfile'],
  495. );
  496. $langs[$row['lngfile']] = $row['lngfile'];
  497. }
  498. // Store this useful data!
  499. $boards[$row['id_board']] = $row['id_board'];
  500. if ($row['id_topic'])
  501. $notify['topics'][$row['id_topic']][] = $row['id_member'];
  502. else
  503. $notify['boards'][$row['id_board']][] = $row['id_member'];
  504. }
  505. $smcFunc['db_free_result']($request);
  506. if (empty($boards))
  507. return true;
  508. // Just get the board names.
  509. $request = $smcFunc['db_query']('', '
  510. SELECT id_board, name
  511. FROM {db_prefix}boards
  512. WHERE id_board IN ({array_int:board_list})',
  513. array(
  514. 'board_list' => $boards,
  515. )
  516. );
  517. $boards = array();
  518. while ($row = $smcFunc['db_fetch_assoc']($request))
  519. $boards[$row['id_board']] = $row['name'];
  520. $smcFunc['db_free_result']($request);
  521. if (empty($boards))
  522. return true;
  523. // Get the actual topics...
  524. $request = $smcFunc['db_query']('', '
  525. SELECT ld.note_type, t.id_topic, t.id_board, t.id_member_started, m.id_msg, m.subject,
  526. b.name AS board_name
  527. FROM {db_prefix}log_digest AS ld
  528. INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ld.id_topic
  529. AND t.id_board IN ({array_int:board_list}))
  530. INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
  531. INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
  532. WHERE ' . ($is_weekly ? 'ld.daily != {int:daily_value}' : 'ld.daily IN (0, 2)'),
  533. array(
  534. 'board_list' => array_keys($boards),
  535. 'daily_value' => 2,
  536. )
  537. );
  538. $types = array();
  539. while ($row = $smcFunc['db_fetch_assoc']($request))
  540. {
  541. if (!isset($types[$row['note_type']][$row['id_board']]))
  542. $types[$row['note_type']][$row['id_board']] = array(
  543. 'lines' => array(),
  544. 'name' => $row['board_name'],
  545. 'id' => $row['id_board'],
  546. );
  547. if ($row['note_type'] == 'reply')
  548. {
  549. if (isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
  550. $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['count']++;
  551. else
  552. $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
  553. 'id' => $row['id_topic'],
  554. 'subject' => un_htmlspecialchars($row['subject']),
  555. 'count' => 1,
  556. );
  557. }
  558. elseif ($row['note_type'] == 'topic')
  559. {
  560. if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
  561. $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
  562. 'id' => $row['id_topic'],
  563. 'subject' => un_htmlspecialchars($row['subject']),
  564. );
  565. }
  566. else
  567. {
  568. if (!isset($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]))
  569. $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']] = array(
  570. 'id' => $row['id_topic'],
  571. 'subject' => un_htmlspecialchars($row['subject']),
  572. 'starter' => $row['id_member_started'],
  573. );
  574. }
  575. $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'] = array();
  576. if (!empty($notify['topics'][$row['id_topic']]))
  577. $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'] = array_merge($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'], $notify['topics'][$row['id_topic']]);
  578. if (!empty($notify['boards'][$row['id_board']]))
  579. $types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'] = array_merge($types[$row['note_type']][$row['id_board']]['lines'][$row['id_topic']]['members'], $notify['boards'][$row['id_board']]);
  580. }
  581. $smcFunc['db_free_result']($request);
  582. if (empty($types))
  583. return true;
  584. // Let's load all the languages into a cache thingy.
  585. $langtxt = array();
  586. foreach ($langs as $lang)
  587. {
  588. loadLanguage('Post', $lang);
  589. loadLanguage('index', $lang);
  590. loadLanguage('EmailTemplates', $lang);
  591. $langtxt[$lang] = array(
  592. 'subject' => $txt['digest_subject_' . ($is_weekly ? 'weekly' : 'daily')],
  593. 'char_set' => $txt['lang_character_set'],
  594. 'intro' => sprintf($txt['digest_intro_' . ($is_weekly ? 'weekly' : 'daily')], $mbname),
  595. 'new_topics' => $txt['digest_new_topics'],
  596. 'topic_lines' => $txt['digest_new_topics_line'],
  597. 'new_replies' => $txt['digest_new_replies'],
  598. 'mod_actions' => $txt['digest_mod_actions'],
  599. 'replies_one' => $txt['digest_new_replies_one'],
  600. 'replies_many' => $txt['digest_new_replies_many'],
  601. 'sticky' => $txt['digest_mod_act_sticky'],
  602. 'lock' => $txt['digest_mod_act_lock'],
  603. 'unlock' => $txt['digest_mod_act_unlock'],
  604. 'remove' => $txt['digest_mod_act_remove'],
  605. 'move' => $txt['digest_mod_act_move'],
  606. 'merge' => $txt['digest_mod_act_merge'],
  607. 'split' => $txt['digest_mod_act_split'],
  608. 'bye' => $txt['regards_team'],
  609. );
  610. }
  611. // Right - send out the silly things - this will take quite some space!
  612. $emails = array();
  613. foreach ($members as $mid => $member)
  614. {
  615. // Right character set!
  616. $context['character_set'] = empty($modSettings['global_character_set']) ? $langtxt[$lang]['char_set'] : $modSettings['global_character_set'];
  617. // Do the start stuff!
  618. $email = array(
  619. 'subject' => $mbname . ' - ' . $langtxt[$lang]['subject'],
  620. 'body' => $member['name'] . ',' . "\n\n" . $langtxt[$lang]['intro'] . "\n" . $scripturl . '?action=profile;area=notification;u=' . $member['id'] . "\n",
  621. 'email' => $member['email'],
  622. );
  623. // All new topics?
  624. if (isset($types['topic']))
  625. {
  626. $titled = false;
  627. foreach ($types['topic'] as $id => $board)
  628. foreach ($board['lines'] as $topic)
  629. if (in_array($mid, $topic['members']))
  630. {
  631. if (!$titled)
  632. {
  633. $email['body'] .= "\n" . $langtxt[$lang]['new_topics'] . ':' . "\n" . '-----------------------------------------------';
  634. $titled = true;
  635. }
  636. $email['body'] .= "\n" . sprintf($langtxt[$lang]['topic_lines'], $topic['subject'], $board['name']);
  637. }
  638. if ($titled)
  639. $email['body'] .= "\n";
  640. }
  641. // What about replies?
  642. if (isset($types['reply']))
  643. {
  644. $titled = false;
  645. foreach ($types['reply'] as $id => $board)
  646. foreach ($board['lines'] as $topic)
  647. if (in_array($mid, $topic['members']))
  648. {
  649. if (!$titled)
  650. {
  651. $email['body'] .= "\n" . $langtxt[$lang]['new_replies'] . ':' . "\n" . '-----------------------------------------------';
  652. $titled = true;
  653. }
  654. $email['body'] .= "\n" . ($topic['count'] == 1 ? sprintf($langtxt[$lang]['replies_one'], $topic['subject']) : sprintf($langtxt[$lang]['replies_many'], $topic['count'], $topic['subject']));
  655. }
  656. if ($titled)
  657. $email['body'] .= "\n";
  658. }
  659. // Finally, moderation actions!
  660. $titled = false;
  661. foreach ($types as $note_type => $type)
  662. {
  663. if ($note_type == 'topic' || $note_type == 'reply')
  664. continue;
  665. foreach ($type as $id => $board)
  666. foreach ($board['lines'] as $topic)
  667. if (in_array($mid, $topic['members']))
  668. {
  669. if (!$titled)
  670. {
  671. $email['body'] .= "\n" . $langtxt[$lang]['mod_actions'] . ':' . "\n" . '-----------------------------------------------';
  672. $titled = true;
  673. }
  674. $email['body'] .= "\n" . sprintf($langtxt[$lang][$note_type], $topic['subject']);
  675. }
  676. }
  677. if ($titled)
  678. $email['body'] .= "\n";
  679. // Then just say our goodbyes!
  680. $email['body'] .= "\n\n" . $txt['regards_team'];
  681. // Send it - low priority!
  682. sendmail($email['email'], $email['subject'], $email['body'], null, null, false, 4);
  683. }
  684. // Clean up...
  685. if ($is_weekly)
  686. {
  687. $smcFunc['db_query']('', '
  688. DELETE FROM {db_prefix}log_digest
  689. WHERE daily != {int:not_daily}',
  690. array(
  691. 'not_daily' => 0,
  692. )
  693. );
  694. $smcFunc['db_query']('', '
  695. UPDATE {db_prefix}log_digest
  696. SET daily = {int:daily_value}
  697. WHERE daily = {int:not_daily}',
  698. array(
  699. 'daily_value' => 2,
  700. 'not_daily' => 0,
  701. )
  702. );
  703. }
  704. else
  705. {
  706. // Clear any only weekly ones, and stop us from sending daily again.
  707. $smcFunc['db_query']('', '
  708. DELETE FROM {db_prefix}log_digest
  709. WHERE daily = {int:daily_value}',
  710. array(
  711. 'daily_value' => 2,
  712. )
  713. );
  714. $smcFunc['db_query']('', '
  715. UPDATE {db_prefix}log_digest
  716. SET daily = {int:both_value}
  717. WHERE daily = {int:no_value}',
  718. array(
  719. 'both_value' => 1,
  720. 'no_value' => 0,
  721. )
  722. );
  723. }
  724. // Just in case the member changes their settings mark this as sent.
  725. $members = array_keys($members);
  726. $smcFunc['db_query']('', '
  727. UPDATE {db_prefix}log_notify
  728. SET sent = {int:is_sent}
  729. WHERE id_member IN ({array_int:member_list})',
  730. array(
  731. 'member_list' => $members,
  732. 'is_sent' => 1,
  733. )
  734. );
  735. // Log we've done it...
  736. return true;
  737. }
  738. /**
  739. * Like the daily stuff - just seven times less regular ;)
  740. */
  741. function scheduled_weekly_digest()
  742. {
  743. global $is_weekly;
  744. // We just pass through to the daily function - avoid duplication!
  745. $is_weekly = true;
  746. return scheduled_daily_digest();
  747. }
  748. /**
  749. * Send a group of emails from the mail queue.
  750. *
  751. * @param type $number the number to send each loop through
  752. * @param type $override_limit bypassing our limit flaf
  753. * @param type $force_send
  754. * @return boolean
  755. */
  756. function ReduceMailQueue($number = false, $override_limit = false, $force_send = false)
  757. {
  758. global $modSettings, $smcFunc, $sourcedir;
  759. // Are we intending another script to be sending out the queue?
  760. if (!empty($modSettings['mail_queue_use_cron']) && empty($force_send))
  761. return false;
  762. // By default send 5 at once.
  763. if (!$number)
  764. $number = empty($modSettings['mail_quantity']) ? 5 : $modSettings['mail_quantity'];
  765. // If we came with a timestamp, and that doesn't match the next event, then someone else has beaten us.
  766. if (isset($_GET['ts']) && $_GET['ts'] != $modSettings['mail_next_send'] && empty($force_send))
  767. return false;
  768. // By default move the next sending on by 10 seconds, and require an affected row.
  769. if (!$override_limit)
  770. {
  771. $delay = !empty($modSettings['mail_queue_delay']) ? $modSettings['mail_queue_delay'] : (!empty($modSettings['mail_limit']) && $modSettings['mail_limit'] < 5 ? 10 : 5);
  772. $smcFunc['db_query']('', '
  773. UPDATE {db_prefix}settings
  774. SET value = {string:next_mail_send}
  775. WHERE variable = {string:mail_next_send}
  776. AND value = {string:last_send}',
  777. array(
  778. 'next_mail_send' => time() + $delay,
  779. 'mail_next_send' => 'mail_next_send',
  780. 'last_send' => $modSettings['mail_next_send'],
  781. )
  782. );
  783. if ($smcFunc['db_affected_rows']() == 0)
  784. return false;
  785. $modSettings['mail_next_send'] = time() + $delay;
  786. }
  787. // If we're not overriding how many are we allow to send?
  788. if (!$override_limit && !empty($modSettings['mail_limit']))
  789. {
  790. list ($mt, $mn) = @explode('|', $modSettings['mail_recent']);
  791. // Nothing worth noting...
  792. if (empty($mn) || $mt < time() - 60)
  793. {
  794. $mt = time();
  795. $mn = $number;
  796. }
  797. // Otherwise we have a few more we can spend?
  798. elseif ($mn < $modSettings['mail_limit'])
  799. {
  800. $mn += $number;
  801. }
  802. // No more I'm afraid, return!
  803. else
  804. return false;
  805. // Reflect that we're about to send some, do it now to be safe.
  806. updateSettings(array('mail_recent' => $mt . '|' . $mn));
  807. }
  808. // Now we know how many we're sending, let's send them.
  809. $request = $smcFunc['db_query']('', '
  810. SELECT /*!40001 SQL_NO_CACHE */ id_mail, recipient, body, subject, headers, send_html, time_sent
  811. FROM {db_prefix}mail_queue
  812. ORDER BY priority ASC, id_mail ASC
  813. LIMIT ' . $number,
  814. array(
  815. )
  816. );
  817. $ids = array();
  818. $emails = array();
  819. while ($row = $smcFunc['db_fetch_assoc']($request))
  820. {
  821. // We want to delete these from the database ASAP, so just get the data and go.
  822. $ids[] = $row['id_mail'];
  823. $emails[] = array(
  824. 'to' => $row['recipient'],
  825. 'body' => $row['body'],
  826. 'subject' => $row['subject'],
  827. 'headers' => $row['headers'],
  828. 'send_html' => $row['send_html'],
  829. 'time_sent' => $row['time_sent'],
  830. );
  831. }
  832. $smcFunc['db_free_result']($request);
  833. // Delete, delete, delete!!!
  834. if (!empty($ids))
  835. $smcFunc['db_query']('', '
  836. DELETE FROM {db_prefix}mail_queue
  837. WHERE id_mail IN ({array_int:mail_list})',
  838. array(
  839. 'mail_list' => $ids,
  840. )
  841. );
  842. // Don't believe we have any left?
  843. if (count($ids) < $number)
  844. {
  845. // Only update the setting if no-one else has beaten us to it.
  846. $smcFunc['db_query']('', '
  847. UPDATE {db_prefix}settings
  848. SET value = {string:no_send}
  849. WHERE variable = {string:mail_next_send}
  850. AND value = {string:last_mail_send}',
  851. array(
  852. 'no_send' => '0',
  853. 'mail_next_send' => 'mail_next_send',
  854. 'last_mail_send' => $modSettings['mail_next_send'],
  855. )
  856. );
  857. }
  858. if (empty($ids))
  859. return false;
  860. if (!empty($modSettings['mail_type']) && $modSettings['smtp_host'] != '')
  861. require_once($sourcedir . '/Subs-Post.php');
  862. // Send each email, yea!
  863. $failed_emails = array();
  864. foreach ($emails as $key => $email)
  865. {
  866. if (empty($modSettings['mail_type']) || $modSettings['smtp_host'] == '')
  867. {
  868. $email['subject'] = strtr($email['subject'], array("\r" => '', "\n" => ''));
  869. if (!empty($modSettings['mail_strip_carriage']))
  870. {
  871. $email['body'] = strtr($email['body'], array("\r" => ''));
  872. $email['headers'] = strtr($email['headers'], array("\r" => ''));
  873. }
  874. // No point logging a specific error here, as we have no language. PHP error is helpful anyway...
  875. $result = mail(strtr($email['to'], array("\r" => '', "\n" => '')), $email['subject'], $email['body'], $email['headers']);
  876. // Try to stop a timeout, this would be bad...
  877. @set_time_limit(300);
  878. if (function_exists('apache_reset_timeout'))
  879. @apache_reset_timeout();
  880. }
  881. else
  882. $result = smtp_mail(array($email['to']), $email['subject'], $email['body'], $email['send_html'] ? $email['headers'] : 'Mime-Version: 1.0' . "\r\n" . $email['headers']);
  883. // Hopefully it sent?
  884. if (!$result)
  885. $failed_emails[] = array($email['to'], $email['body'], $email['subject'], $email['headers'], $email['send_html'], $email['time_sent']);
  886. }
  887. // Any emails that didn't send?
  888. if (!empty($failed_emails))
  889. {
  890. // Update the failed attempts check.
  891. $smcFunc['db_insert']('replace',
  892. '{db_prefix}settings',
  893. array('variable' => 'string', 'value' => 'string'),
  894. array('mail_failed_attempts', empty($modSettings['mail_failed_attempts']) ? 1 : ++$modSettings['mail_failed_attempts']),
  895. array('variable')
  896. );
  897. // If we have failed to many times, tell mail to wait a bit and try again.
  898. if ($modSettings['mail_failed_attempts'] > 5)
  899. $smcFunc['db_query']('', '
  900. UPDATE {db_prefix}settings
  901. SET value = {string:mail_next_send}
  902. WHERE variable = {string:next_mail_send}
  903. AND value = {string:last_send}',
  904. array(
  905. 'next_mail_send' => time() + 60,
  906. 'mail_next_send' => 'mail_next_send',
  907. 'last_send' => $modSettings['mail_next_send'],
  908. ));
  909. // Add our email back to the queue, manually.
  910. $smcFunc['db_insert']('insert',
  911. '{db_prefix}mail_queue',
  912. array('recipient' => 'string', 'body' => 'string', 'subject' => 'string', 'headers' => 'string', 'send_html' => 'string', 'time_sent' => 'string'),
  913. $failed_emails,
  914. array('id_mail')
  915. );
  916. return false;
  917. }
  918. // We where unable to send the email, clear our failed attempts.
  919. elseif (!empty($modSettings['mail_failed_attempts']))
  920. $smcFunc['db_query']('', '
  921. UPDATE {db_prefix}settings
  922. SET value = {string:zero}
  923. WHERE variable = {string:mail_failed_attempts}',
  924. array(
  925. 'zero' => '0',
  926. 'mail_failed_attempts' => 'mail_failed_attempts',
  927. ));
  928. // Had something to send...
  929. return true;
  930. }
  931. /**
  932. * Calculate the next time the passed tasks should be triggered.
  933. *
  934. * @param type $tasks
  935. * @param type $forceUpdate
  936. */
  937. function CalculateNextTrigger($tasks = array(), $forceUpdate = false)
  938. {
  939. global $modSettings, $smcFunc;
  940. $task_query = '';
  941. if (!is_array($tasks))
  942. $tasks = array($tasks);
  943. // Actually have something passed?
  944. if (!empty($tasks))
  945. {
  946. if (!isset($tasks[0]) || is_numeric($tasks[0]))
  947. $task_query = ' AND id_task IN ({array_int:tasks})';
  948. else
  949. $task_query = ' AND task IN ({array_string:tasks})';
  950. }
  951. $nextTaskTime = empty($tasks) ? time() + 86400 : $modSettings['next_task_time'];
  952. // Get the critical info for the tasks.
  953. $request = $smcFunc['db_query']('', '
  954. SELECT id_task, next_time, time_offset, time_regularity, time_unit
  955. FROM {db_prefix}scheduled_tasks
  956. WHERE disabled = {int:no_disabled}
  957. ' . $task_query,
  958. array(
  959. 'no_disabled' => 0,
  960. 'tasks' => $tasks,
  961. )
  962. );
  963. $tasks = array();
  964. while ($row = $smcFunc['db_fetch_assoc']($request))
  965. {
  966. $next_time = next_time($row['time_regularity'], $row['time_unit'], $row['time_offset']);
  967. // Only bother moving the task if it's out of place or we're forcing it!
  968. if ($forceUpdate || $next_time < $row['next_time'] || $row['next_time'] < time())
  969. $tasks[$row['id_task']] = $next_time;
  970. else
  971. $next_time = $row['next_time'];
  972. // If this is sooner than the current next task, make this the next task.
  973. if ($next_time < $nextTaskTime)
  974. $nextTaskTime = $next_time;
  975. }
  976. $smcFunc['db_free_result']($request);
  977. // Now make the changes!
  978. foreach ($tasks as $id => $time)
  979. $smcFunc['db_query']('', '
  980. UPDATE {db_prefix}scheduled_tasks
  981. SET next_time = {int:next_time}
  982. WHERE id_task = {int:id_task}',
  983. array(
  984. 'next_time' => $time,
  985. 'id_task' => $id,
  986. )
  987. );
  988. // If the next task is now different update.
  989. if ($modSettings['next_task_time'] != $nextTaskTime)
  990. updateSettings(array('next_task_time' => $nextTaskTime));
  991. }
  992. /**
  993. * Simply returns a time stamp of the next instance of these time parameters.
  994. *
  995. * @param int $regularity
  996. * @param type $unit
  997. * @param type $offset
  998. * @return int
  999. */
  1000. function next_time($regularity, $unit, $offset)
  1001. {
  1002. // Just in case!
  1003. if ($regularity == 0)
  1004. $regularity = 2;
  1005. $curHour = date('H', time());
  1006. $curMin = date('i', time());
  1007. $next_time = 9999999999;
  1008. // If the unit is minutes only check regularity in minutes.
  1009. if ($unit == 'm')
  1010. {
  1011. $off = date('i', $offset);
  1012. // If it's now just pretend it ain't,
  1013. if ($off == $curMin)
  1014. $next_time = time() + $regularity;
  1015. else
  1016. {
  1017. // Make sure that the offset is always in the past.
  1018. $off = $off > $curMin ? $off - 60 : $off;
  1019. while ($off <= $curMin)
  1020. $off += $regularity;
  1021. // Now we know when the time should be!
  1022. $next_time = time() + 60 * ($off - $curMin);
  1023. }
  1024. }
  1025. // Otherwise, work out what the offset would be with todays date.
  1026. else
  1027. {
  1028. $next_time = mktime(date('H', $offset), date('i', $offset), 0, date('m'), date('d'), date('Y'));
  1029. // Make the time offset in the past!
  1030. if ($next_time > time())
  1031. {
  1032. $next_time -= 86400;
  1033. }
  1034. // Default we'll jump in hours.
  1035. $applyOffset = 3600;
  1036. // 24 hours = 1 day.
  1037. if ($unit == 'd')
  1038. $applyOffset = 86400;
  1039. // Otherwise a week.
  1040. if ($unit == 'w')
  1041. $applyOffset = 604800;
  1042. $applyOffset *= $regularity;
  1043. // Just add on the offset.
  1044. while ($next_time <= time())
  1045. {
  1046. $next_time += $applyOffset;
  1047. }
  1048. }
  1049. return $next_time;
  1050. }
  1051. /**
  1052. * This loads the bare minimum data to allow us to load language files!
  1053. */
  1054. function loadEssentialThemeData()
  1055. {
  1056. global $settings, $modSettings, $smcFunc, $mbname, $context, $sourcedir;
  1057. // Get all the default theme variables.
  1058. $result = $smcFunc['db_query']('', '
  1059. SELECT id_theme, variable, value
  1060. FROM {db_prefix}themes
  1061. WHERE id_member = {int:no_member}
  1062. AND id_theme IN (1, {int:theme_guests})',
  1063. array(
  1064. 'no_member' => 0,
  1065. 'theme_guests' => $modSettings['theme_guests'],
  1066. )
  1067. );
  1068. while ($row = $smcFunc['db_fetch_assoc']($result))
  1069. {
  1070. $settings[$row['variable']] = $row['value'];
  1071. // Is this the default theme?
  1072. if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1')
  1073. $settings['default_' . $row['variable']] = $row['value'];
  1074. }
  1075. $smcFunc['db_free_result']($result);
  1076. // Check we have some directories setup.
  1077. if (empty($settings['template_dirs']))
  1078. {
  1079. $settings['template_dirs'] = array($settings['theme_dir']);
  1080. // Based on theme (if there is one).
  1081. if (!empty($settings['base_theme_dir']))
  1082. $settings['template_dirs'][] = $settings['base_theme_dir'];
  1083. // Lastly the default theme.
  1084. if ($settings['theme_dir'] != $settings['default_theme_dir'])
  1085. $settings['template_dirs'][] = $settings['default_theme_dir'];
  1086. }
  1087. // Assume we want this.
  1088. $context['forum_name'] = $mbname;
  1089. // Check loadLanguage actually exists!
  1090. if (!function_exists('loadLanguage'))
  1091. {
  1092. require_once($sourcedir . '/Load.php');
  1093. require_once($sourcedir . '/Subs.php');
  1094. }
  1095. loadLanguage('index+Modifications');
  1096. }
  1097. /**
  1098. * This retieves data (e.g. last version of SMF) from sm.org
  1099. */
  1100. function scheduled_fetchSMfiles()
  1101. {
  1102. global $sourcedir, $txt, $language, $settings, $forum_version, $modSettings, $smcFunc;
  1103. // What files do we want to get
  1104. $request = $smcFunc['db_query']('', '
  1105. SELECT id_file, filename, path, parameters
  1106. FROM {db_prefix}admin_info_files',
  1107. array(
  1108. )
  1109. );
  1110. $js_files = array();
  1111. while ($row = $smcFunc['db_fetch_assoc']($request))
  1112. {
  1113. $js_files[$row['id_file']] = array(
  1114. 'filename' => $row['filename'],
  1115. 'path' => $row['path'],
  1116. 'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode($forum_version)),
  1117. );
  1118. }
  1119. $smcFunc['db_free_result']($request);
  1120. // We're gonna need fetch_web_data() to pull this off.
  1121. require_once($sourcedir . '/Subs-Package.php');
  1122. // Just in case we run into a problem.
  1123. loadEssentialThemeData();
  1124. loadLanguage('Errors', $language, false);
  1125. foreach ($js_files as $ID_FILE => $file)
  1126. {
  1127. // Create the url
  1128. $server = empty($file['path']) || substr($file['path'], 0, 7) != 'http://' ? 'http://www.simplemachines.org' : '';
  1129. $url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : '');
  1130. // Get the file
  1131. $file_data = fetch_web_data($url);
  1132. // If we got an error - give up - the site might be down.
  1133. if ($file_data === false)
  1134. {
  1135. log_error(sprintf($txt['st_cannot_retrieve_file'], $url));
  1136. return false;
  1137. }
  1138. // Save the file to the database.
  1139. $smcFunc['db_query']('substring', '
  1140. UPDATE {db_prefix}admin_info_files
  1141. SET data = SUBSTRING({string:file_data}, 1, 65534)
  1142. WHERE id_file = {int:id_file}',
  1143. array(
  1144. 'id_file' => $ID_FILE,
  1145. 'file_data' => $file_data,
  1146. )
  1147. );
  1148. }
  1149. return true;
  1150. }
  1151. /**
  1152. * Happy birthday!!
  1153. */
  1154. function scheduled_birthdayemails()
  1155. {
  1156. global $modSettings, $sourcedir, $mbname, $txt, $smcFunc, $birthdayEmails;
  1157. // Need this in order to load the language files.
  1158. loadEssentialThemeData();
  1159. // Going to need this to send the emails.
  1160. require_once($sourcedir . '/Subs-Post.php');
  1161. $greeting = isset($modSettings['birthday_email']) ? $modSettings['birthday_email'] : 'happy_birthday';
  1162. // Get the month and day of today.
  1163. $month = date('n'); // Month without leading zeros.
  1164. $day = date('j'); // Day without leading zeros.
  1165. // So who are the lucky ones? Don't include those who are banned and those who don't want them.
  1166. $result = $smcFunc['db_query']('', '
  1167. SELECT id_member, real_name, lngfile, email_address
  1168. FROM {db_prefix}members
  1169. WHERE is_activated < 10
  1170. AND MONTH(birthdate) = {int:month}
  1171. AND DAYOFMONTH(birthdate) = {int:day}
  1172. AND notify_announcements = {int:notify_announcements}
  1173. AND YEAR(birthdate) > {int:year}',
  1174. array(
  1175. 'notify_announcements' => 1,
  1176. 'year' => 1,
  1177. 'month' => $month,
  1178. 'day' => $day,
  1179. )
  1180. );
  1181. // Group them by languages.
  1182. $birthdays = array();
  1183. while ($row = $smcFunc['db_fetch_assoc']($result))
  1184. {
  1185. if (!isset($birthdays[$row['lngfile']]))
  1186. $birthdays[$row['lngfile']] = array();
  1187. $birthdays[$row['lngfile']][$row['id_member']] = array(
  1188. 'name' => $row['real_name'],
  1189. 'email' => $row['email_address']
  1190. );
  1191. }
  1192. $smcFunc['db_free_result']($result);
  1193. // Send out the greetings!
  1194. foreach ($birthdays as $lang => $recps)
  1195. {
  1196. // We need to do some shuffling to make this work properly.
  1197. loadLanguage('EmailTemplates', $lang);
  1198. $txt['emails']['happy_birthday']['subject'] = $txtBirthdayEmails[$greeting . '_subject'];
  1199. $txt['emails']['happy_birthday']['body'] = $txtBirthdayEmails[$greeting . '_body'];
  1200. foreach ($recps as $recp)
  1201. {
  1202. $replacements = array(
  1203. 'REALNAME' => $recp['name'],
  1204. );
  1205. $emaildata = loadEmailTemplate('happy_birthday', $replacements, $lang, false);
  1206. sendmail($recp['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 4);
  1207. // Try to stop a timeout, this would be bad...
  1208. @set_time_limit(300);
  1209. if (function_exists('apache_reset_timeout'))
  1210. @apache_reset_timeout();
  1211. }
  1212. }
  1213. // Flush the mail queue, just in case.
  1214. AddMailQueue(true);
  1215. return true;
  1216. }
  1217. /**
  1218. * Weekly maintenance
  1219. */
  1220. function scheduled_weekly_maintenance()
  1221. {
  1222. global $modSettings, $smcFunc;
  1223. // Delete some settings that needn't be set if they are otherwise empty.
  1224. $emptySettings = array(
  1225. 'warning_mute', 'warning_moderate', 'warning_watch', 'warning_show', 'disableCustomPerPage', 'spider_mode', 'spider_group',
  1226. 'paid_currency_code', 'paid_currency_symbol', 'paid_email_to', 'paid_email', 'paid_enabled', 'paypal_email',
  1227. 'search_enable_captcha', 'search_floodcontrol_time', 'show_spider_online',
  1228. );
  1229. $smcFunc['db_query']('', '
  1230. DELETE FROM {db_prefix}settings
  1231. WHERE variable IN ({array_string:setting_list})
  1232. AND (value = {string:zero_value} OR value = {string:blank_value})',
  1233. array(
  1234. 'zero_value' => '0',
  1235. 'blank_value' => '',
  1236. 'setting_list' => $emptySettings,
  1237. )
  1238. );
  1239. // Some settings we never want to keep - they are just there for temporary purposes.
  1240. $deleteAnywaySettings = array(
  1241. 'attachment_full_notified',
  1242. );
  1243. $smcFunc['db_query']('', '
  1244. DELETE FROM {db_prefix}settings
  1245. WHERE variable IN ({array_string:setting_list})',
  1246. array(
  1247. 'setting_list' => $deleteAnywaySettings,
  1248. )
  1249. );
  1250. // Ok should we prune the logs?
  1251. if (!empty($modSettings['pruningOptions']))
  1252. {
  1253. if (!empty($modSettings['pruningOptions']) && strpos($modSettings['pruningOptions'], ',') !== false)
  1254. list ($modSettings['pruneErrorLog'], $modSettings['pruneModLog'], $modSettings['pruneBanLog'], $modSettings['pruneReportLog'], $modSettings['pruneScheduledTaskLog'], $modSettings['pruneSpiderHitLog']) = explode(',', $modSettings['pruningOptions']);
  1255. if (!empty($modSettings['pruneErrorLog']))
  1256. {
  1257. // Figure out when our cutoff time is. 1 day = 86400 seconds.
  1258. $t = time() - $modSettings['pruneErrorLog'] * 86400;
  1259. $smcFunc['db_query']('', '
  1260. DELETE FROM {db_prefix}log_errors
  1261. WHERE log_time < {int:log_time}',
  1262. array(
  1263. 'log_time' => $t,
  1264. )
  1265. );
  1266. }
  1267. if (!empty($modSettings['pruneModLog']))
  1268. {
  1269. // Figure out when our cutoff time is. 1 day = 86400 seconds.
  1270. $t = time() - $modSettings['pruneModLog'] * 86400;
  1271. $smcFunc['db_query']('', '
  1272. DELETE FROM {db_prefix}log_actions
  1273. WHERE log_time < {int:log_time}
  1274. AND id_log = {int:moderation_log}',
  1275. array(
  1276. 'log_time' => $t,
  1277. 'moderation_log' => 1,
  1278. )
  1279. );
  1280. }
  1281. if (!empty($modSettings['pruneBanLog']))
  1282. {
  1283. // Figure out when our cutoff time is. 1 day = 86400 seconds.
  1284. $t = time() - $modSettings['pruneBanLog'] * 86400;
  1285. $smcFunc['db_query']('', '
  1286. DELETE FROM {db_prefix}log_banned
  1287. WHERE log_time < {int:log_time}',
  1288. array(
  1289. 'log_time' => $t,
  1290. )
  1291. );
  1292. }
  1293. if (!empty($modSettings['pruneReportLog']))
  1294. {
  1295. // Figure out when our cutoff time is. 1 day = 86400 seconds.
  1296. $t = time() - $modSettings['pruneReportLog'] * 86400;
  1297. // This one is more complex then the other logs. First we need to figure out which reports are too old.
  1298. $reports = array();
  1299. $result = $smcFunc['db_query']('', '
  1300. SELECT id_report
  1301. FROM {db_prefix}log_reported
  1302. WHERE time_started < {int:time_started}',
  1303. array(
  1304. 'time_started' => $t,
  1305. )
  1306. );
  1307. while ($row = $smcFunc['db_fetch_row']($result))
  1308. $reports[] = $row[0];
  1309. $smcFunc['db_free_result']($result);
  1310. if (!empty($reports))
  1311. {
  1312. // Now delete the reports...
  1313. $smcFunc['db_query']('', '
  1314. DELETE FROM {db_prefix}log_reported
  1315. WHERE id_report IN ({array_int:report_list})',
  1316. array(
  1317. 'report_list' => $reports,
  1318. )
  1319. );
  1320. // And delete the comments for those reports...
  1321. $smcFunc['db_query']('', '
  1322. DELETE FROM {db_prefix}log_reported_comments
  1323. WHERE id_report IN ({array_int:report_list})',
  1324. array(
  1325. 'report_list' => $reports,
  1326. )
  1327. );
  1328. }
  1329. }
  1330. if (!empty($modSettings['pruneScheduledTaskLog']))
  1331. {
  1332. // Figure out when our cutoff time is. 1 day = 86400 seconds.
  1333. $t = time() - $modSettings['pruneScheduledTaskLog'] * 86400;
  1334. $smcFunc['db_query']('', '
  1335. DELETE FROM {db_prefix}log_scheduled_tasks
  1336. WHERE time_run < {int:time_run}',
  1337. array(
  1338. 'time_run' => $t,
  1339. )
  1340. );
  1341. }
  1342. if (!empty($modSettings['pruneSpiderHitLog']))
  1343. {
  1344. // Figure out when our cutoff time is. 1 day = 86400 seconds.
  1345. $t = time() - $modSettings['pruneSpiderHitLog'] * 86400;
  1346. $smcFunc['db_query']('', '
  1347. DELETE FROM {db_prefix}log_spider_hits
  1348. WHERE log_time < {int:log_time}',
  1349. array(
  1350. 'log_time' => $t,
  1351. )
  1352. );
  1353. }
  1354. }
  1355. // Get rid of any paid subscriptions that were never actioned.
  1356. $smcFunc['db_query']('', '
  1357. DELETE FROM {db_prefix}log_subscribed
  1358. WHERE end_time = {int:no_end_time}
  1359. AND status = {int:not_active}
  1360. AND start_time < {int:start_time}
  1361. AND payments_pending < {int:payments_pending}',
  1362. array(
  1363. 'no_end_time' => 0,
  1364. 'not_active' => 0,
  1365. 'start_time' => time() - 60,
  1366. 'payments_pending' => 1,
  1367. )
  1368. );
  1369. // Some OS's don't seem to clean out their sessions.
  1370. $smcFunc['db_query']('', '
  1371. DELETE FROM {db_prefix}sessions
  1372. WHERE last_update < {int:last_update}',
  1373. array(
  1374. 'last_update' => time() - 86400,
  1375. )
  1376. );
  1377. return true;
  1378. }
  1379. /**
  1380. * Perform the standard checks on expiring/near expiring subscriptions.
  1381. */
  1382. function scheduled_paid_subscriptions()
  1383. {
  1384. global $txt, $sourcedir, $scripturl, $smcFunc, $modSettings, $language;
  1385. // Start off by checking for removed subscriptions.
  1386. $request = $smcFunc['db_query']('', '
  1387. SELECT id_subscribe, id_member
  1388. FROM {db_prefix}log_subscribed
  1389. WHERE status = {int:is_active}
  1390. AND end_time < {int:time_now}',
  1391. array(
  1392. 'is_active' => 1,
  1393. 'time_now' => time(),
  1394. )
  1395. );
  1396. while ($row = $smcFunc['db_fetch_assoc']($request))
  1397. {
  1398. require_once($sourcedir . '/ManagePaid.php');
  1399. removeSubscription($row['id_subscribe'], $row['id_member']);
  1400. }
  1401. $smcFunc['db_free_result']($request);
  1402. // Get all those about to expire that have not had a reminder sent.
  1403. $request = $smcFunc['db_query']('', '
  1404. SELECT ls.id_sublog, m.id_member, m.member_name, m.email_address, m.lngfile, s.name, ls.end_time
  1405. FROM {db_prefix}log_subscribed AS ls
  1406. INNER JOIN {db_prefix}subscriptions AS s ON (s.id_subscribe = ls.id_subscribe)
  1407. INNER JOIN {db_prefix}members AS m ON (m.id_member = ls.id_member)
  1408. WHERE ls.status = {int:is_active}
  1409. AND ls.reminder_sent = {int:reminder_sent}
  1410. AND s.reminder > {int:reminder_wanted}
  1411. AND ls.end_time < ({int:time_now} + s.reminder * 86400)',
  1412. array(
  1413. 'is_active' => 1,
  1414. 'reminder_sent' => 0,
  1415. 'reminder_wanted' => 0,
  1416. 'time_now' => time(),
  1417. )
  1418. );
  1419. $subs_reminded = array();
  1420. while ($row = $smcFunc['db_fetch_assoc']($request))
  1421. {
  1422. // If this is the first one load the important bits.
  1423. if (empty($subs_reminded))
  1424. {
  1425. require_once($sourcedir . '/Subs-Post.php');
  1426. // Need the below for loadLanguage to work!
  1427. loadEssentialThemeData();
  1428. }
  1429. $subs_reminded[] = $row['id_sublog'];
  1430. $replacements = array(
  1431. 'PROFILE_LINK' => $scripturl . '?action=profile;area=subscriptions;u=' . $row['id_member'],
  1432. 'REALNAME' => $row['member_name'],
  1433. 'SUBSCRIPTION' => $row['name'],
  1434. 'END_DATE' => strip_tags(timeformat($row['end_time'])),
  1435. );
  1436. $emaildata = loadEmailTemplate('paid_subscription_reminder', $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']);
  1437. // Send the actual email.
  1438. sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 2);
  1439. }
  1440. $smcFunc['db_free_result']($request);
  1441. // Mark the reminder as sent.
  1442. if (!empty($subs_reminded))
  1443. $smcFunc['db_query']('', '
  1444. UPDATE {db_prefix}log_subscribed
  1445. SET reminder_sent = {int:reminder_sent}
  1446. WHERE id_sublog IN ({array_int:subscription_list})',
  1447. array(
  1448. 'subscription_list' => $subs_reminded,
  1449. 'reminder_sent' => 1,
  1450. )
  1451. );
  1452. return true;
  1453. }
  1454. /**
  1455. * Check for un-posted attachments is something we can do once in a while :P
  1456. * This function uses opendir cycling through all the attachments
  1457. */
  1458. function scheduled_remove_temp_attachments()
  1459. {
  1460. global $modSettings;
  1461. // We need to know where this thing is going.
  1462. if (!empty($modSettings['currentAttachmentUploadDir']))
  1463. {
  1464. if (!is_array($modSettings['attachmentUploadDir']))
  1465. $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
  1466. // Just use the current path for temp files.
  1467. $attach_dirs = $modSettings['attachmentUploadDir'];
  1468. }
  1469. else
  1470. {
  1471. $attach_dirs = array($modSettings['attachmentUploadDir']);
  1472. }
  1473. foreach ($attach_dirs as $attach_dir)
  1474. {
  1475. $dir = @opendir($attach_dir) or fatal_lang_error('cant_access_upload_path', 'critical');
  1476. while ($file = readdir($dir))
  1477. {
  1478. if ($file == '.' || $file == '..')
  1479. continue;
  1480. if (strpos($file, 'post_tmp_') !== false)
  1481. {
  1482. // Temp file is more than 5 hours old!
  1483. if (filemtime($attach_dir . '/' . $file) < time() - 18000)
  1484. @unlink($attach_dir . '/' . $file);
  1485. }
  1486. }
  1487. closedir($dir);
  1488. }
  1489. }
  1490. /**
  1491. * Check for move topic notices that have past their best by date
  1492. */
  1493. function scheduled_remove_topic_redirect()
  1494. {
  1495. global $smcFunc, $sourcedir;
  1496. // init
  1497. $topics = array();
  1498. // We will need this for lanaguage files
  1499. loadEssentialThemeData();
  1500. // Find all of the old MOVE topic notices that were set to expire
  1501. $request = $smcFunc['db_query']('', '
  1502. SELECT id_topic
  1503. FROM {db_prefix}topics
  1504. WHERE redirect_expires <= {int:redirect_expires}
  1505. AND redirect_expires <> 0',
  1506. array(
  1507. 'redirect_expires' => time(),
  1508. )
  1509. );
  1510. while ($row = $smcFunc['db_fetch_row']($request))
  1511. $topics[] = $row[0];
  1512. $smcFunc['db_free_result']($request);
  1513. // Zap, your gone
  1514. if (count($topics) > 0)
  1515. {
  1516. require_once($sourcedir . '/RemoveTopic.php');
  1517. removeTopics($topics, false, true);
  1518. }
  1519. return true;
  1520. }
  1521. /**
  1522. * Check for old drafts and remove them
  1523. */
  1524. function scheduled_remove_old_drafts()
  1525. {
  1526. global $smcFunc, $sourcedir, $modSettings;
  1527. if (empty($modSettings['drafts_keep_days']))
  1528. return true;
  1529. // init
  1530. $drafts= array();
  1531. // We need this for lanaguage items
  1532. loadEssentialThemeData();
  1533. // Find all of the old drafts
  1534. $request = $smcFunc['db_query']('', '
  1535. SELECT id_draft
  1536. FROM {db_prefix}user_drafts
  1537. WHERE poster_time <= {int:poster_time_old}',
  1538. array(
  1539. 'poster_time_old' => time() - (86400 * $modSettings['drafts_keep_days']),
  1540. )
  1541. );
  1542. while ($row = $smcFunc['db_fetch_row']($request))
  1543. $drafts[] = (int) $row[0];
  1544. $smcFunc['db_free_result']($request);
  1545. // If we have old one, remove them
  1546. if (count($drafts) > 0)
  1547. {
  1548. require_once($sourcedir . '/Drafts.php');
  1549. DeleteDraft($drafts, false);
  1550. }
  1551. return true;
  1552. }
  1553. ?>