ScheduledTasks.php 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798
  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 2014 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, $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, $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, 'schedapp', 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, 'digest', 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 = {literal:mail_next_send}
  776. AND value = {string:last_send}',
  777. array(
  778. 'next_mail_send' => time() + $delay,
  779. 'last_send' => $modSettings['mail_next_send'],
  780. )
  781. );
  782. if ($smcFunc['db_affected_rows']() == 0)
  783. return false;
  784. $modSettings['mail_next_send'] = time() + $delay;
  785. }
  786. // If we're not overriding how many are we allow to send?
  787. if (!$override_limit && !empty($modSettings['mail_limit']))
  788. {
  789. list ($mt, $mn) = @explode('|', $modSettings['mail_recent']);
  790. // Nothing worth noting...
  791. if (empty($mn) || $mt < time() - 60)
  792. {
  793. $mt = time();
  794. $mn = $number;
  795. }
  796. // Otherwise we have a few more we can spend?
  797. elseif ($mn < $modSettings['mail_limit'])
  798. {
  799. $mn += $number;
  800. }
  801. // No more I'm afraid, return!
  802. else
  803. return false;
  804. // Reflect that we're about to send some, do it now to be safe.
  805. updateSettings(array('mail_recent' => $mt . '|' . $mn));
  806. }
  807. // Now we know how many we're sending, let's send them.
  808. $request = $smcFunc['db_query']('', '
  809. SELECT /*!40001 SQL_NO_CACHE */ id_mail, recipient, body, subject, headers, send_html, time_sent, private
  810. FROM {db_prefix}mail_queue
  811. ORDER BY priority ASC, id_mail ASC
  812. LIMIT ' . $number,
  813. array(
  814. )
  815. );
  816. $ids = array();
  817. $emails = array();
  818. while ($row = $smcFunc['db_fetch_assoc']($request))
  819. {
  820. // We want to delete these from the database ASAP, so just get the data and go.
  821. $ids[] = $row['id_mail'];
  822. $emails[] = array(
  823. 'to' => $row['recipient'],
  824. 'body' => $row['body'],
  825. 'subject' => $row['subject'],
  826. 'headers' => $row['headers'],
  827. 'send_html' => $row['send_html'],
  828. 'time_sent' => $row['time_sent'],
  829. 'private' => $row['private'],
  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 = {literal:mail_next_send}
  850. AND value = {string:last_mail_send}',
  851. array(
  852. 'no_send' => '0',
  853. 'last_mail_send' => $modSettings['mail_next_send'],
  854. )
  855. );
  856. }
  857. if (empty($ids))
  858. return false;
  859. if (!empty($modSettings['mail_type']) && $modSettings['smtp_host'] != '')
  860. require_once($sourcedir . '/Subs-Post.php');
  861. // Send each email, yea!
  862. $failed_emails = array();
  863. foreach ($emails as $key => $email)
  864. {
  865. if (empty($modSettings['mail_type']) || $modSettings['smtp_host'] == '')
  866. {
  867. $email['subject'] = strtr($email['subject'], array("\r" => '', "\n" => ''));
  868. if (!empty($modSettings['mail_strip_carriage']))
  869. {
  870. $email['body'] = strtr($email['body'], array("\r" => ''));
  871. $email['headers'] = strtr($email['headers'], array("\r" => ''));
  872. }
  873. // No point logging a specific error here, as we have no language. PHP error is helpful anyway...
  874. $result = mail(strtr($email['to'], array("\r" => '', "\n" => '')), $email['subject'], $email['body'], $email['headers']);
  875. // Try to stop a timeout, this would be bad...
  876. @set_time_limit(300);
  877. if (function_exists('apache_reset_timeout'))
  878. @apache_reset_timeout();
  879. }
  880. else
  881. $result = smtp_mail(array($email['to']), $email['subject'], $email['body'], $email['send_html'] ? $email['headers'] : 'Mime-Version: 1.0' . "\r\n" . $email['headers']);
  882. // Hopefully it sent?
  883. if (!$result)
  884. $failed_emails[] = array($email['to'], $email['body'], $email['subject'], $email['headers'], $email['send_html'], $email['time_sent'], $email['private']);
  885. }
  886. // Any emails that didn't send?
  887. if (!empty($failed_emails))
  888. {
  889. // Update the failed attempts check.
  890. $smcFunc['db_insert']('replace',
  891. '{db_prefix}settings',
  892. array('variable' => 'string', 'value' => 'string'),
  893. array('mail_failed_attempts', empty($modSettings['mail_failed_attempts']) ? 1 : ++$modSettings['mail_failed_attempts']),
  894. array('variable')
  895. );
  896. // If we have failed to many times, tell mail to wait a bit and try again.
  897. if ($modSettings['mail_failed_attempts'] > 5)
  898. $smcFunc['db_query']('', '
  899. UPDATE {db_prefix}settings
  900. SET value = {string:next_mail_send}
  901. WHERE variable = {literal:mail_next_send}
  902. AND value = {string:last_send}',
  903. array(
  904. 'next_mail_send' => time() + 60,
  905. 'last_send' => $modSettings['mail_next_send'],
  906. ));
  907. // Add our email back to the queue, manually.
  908. $smcFunc['db_insert']('insert',
  909. '{db_prefix}mail_queue',
  910. array('recipient' => 'string', 'body' => 'string', 'subject' => 'string', 'headers' => 'string', 'send_html' => 'string', 'time_sent' => 'string', 'private' => 'int'),
  911. $failed_emails,
  912. array('id_mail')
  913. );
  914. return false;
  915. }
  916. // We where unable to send the email, clear our failed attempts.
  917. elseif (!empty($modSettings['mail_failed_attempts']))
  918. $smcFunc['db_query']('', '
  919. UPDATE {db_prefix}settings
  920. SET value = {string:zero}
  921. WHERE variable = {string:mail_failed_attempts}',
  922. array(
  923. 'zero' => '0',
  924. 'mail_failed_attempts' => 'mail_failed_attempts',
  925. ));
  926. // Had something to send...
  927. return true;
  928. }
  929. /**
  930. * Calculate the next time the passed tasks should be triggered.
  931. *
  932. * @param type $tasks
  933. * @param type $forceUpdate
  934. */
  935. function CalculateNextTrigger($tasks = array(), $forceUpdate = false)
  936. {
  937. global $modSettings, $smcFunc;
  938. $task_query = '';
  939. if (!is_array($tasks))
  940. $tasks = array($tasks);
  941. // Actually have something passed?
  942. if (!empty($tasks))
  943. {
  944. if (!isset($tasks[0]) || is_numeric($tasks[0]))
  945. $task_query = ' AND id_task IN ({array_int:tasks})';
  946. else
  947. $task_query = ' AND task IN ({array_string:tasks})';
  948. }
  949. $nextTaskTime = empty($tasks) ? time() + 86400 : $modSettings['next_task_time'];
  950. // Get the critical info for the tasks.
  951. $request = $smcFunc['db_query']('', '
  952. SELECT id_task, next_time, time_offset, time_regularity, time_unit
  953. FROM {db_prefix}scheduled_tasks
  954. WHERE disabled = {int:no_disabled}
  955. ' . $task_query,
  956. array(
  957. 'no_disabled' => 0,
  958. 'tasks' => $tasks,
  959. )
  960. );
  961. $tasks = array();
  962. while ($row = $smcFunc['db_fetch_assoc']($request))
  963. {
  964. $next_time = next_time($row['time_regularity'], $row['time_unit'], $row['time_offset']);
  965. // Only bother moving the task if it's out of place or we're forcing it!
  966. if ($forceUpdate || $next_time < $row['next_time'] || $row['next_time'] < time())
  967. $tasks[$row['id_task']] = $next_time;
  968. else
  969. $next_time = $row['next_time'];
  970. // If this is sooner than the current next task, make this the next task.
  971. if ($next_time < $nextTaskTime)
  972. $nextTaskTime = $next_time;
  973. }
  974. $smcFunc['db_free_result']($request);
  975. // Now make the changes!
  976. foreach ($tasks as $id => $time)
  977. $smcFunc['db_query']('', '
  978. UPDATE {db_prefix}scheduled_tasks
  979. SET next_time = {int:next_time}
  980. WHERE id_task = {int:id_task}',
  981. array(
  982. 'next_time' => $time,
  983. 'id_task' => $id,
  984. )
  985. );
  986. // If the next task is now different update.
  987. if ($modSettings['next_task_time'] != $nextTaskTime)
  988. updateSettings(array('next_task_time' => $nextTaskTime));
  989. }
  990. /**
  991. * Simply returns a time stamp of the next instance of these time parameters.
  992. *
  993. * @param int $regularity
  994. * @param type $unit
  995. * @param type $offset
  996. * @return int
  997. */
  998. function next_time($regularity, $unit, $offset)
  999. {
  1000. // Just in case!
  1001. if ($regularity == 0)
  1002. $regularity = 2;
  1003. $curHour = date('H', time());
  1004. $curMin = date('i', time());
  1005. $next_time = 9999999999;
  1006. // If the unit is minutes only check regularity in minutes.
  1007. if ($unit == 'm')
  1008. {
  1009. $off = date('i', $offset);
  1010. // If it's now just pretend it ain't,
  1011. if ($off == $curMin)
  1012. $next_time = time() + $regularity;
  1013. else
  1014. {
  1015. // Make sure that the offset is always in the past.
  1016. $off = $off > $curMin ? $off - 60 : $off;
  1017. while ($off <= $curMin)
  1018. $off += $regularity;
  1019. // Now we know when the time should be!
  1020. $next_time = time() + 60 * ($off - $curMin);
  1021. }
  1022. }
  1023. // Otherwise, work out what the offset would be with todays date.
  1024. else
  1025. {
  1026. $next_time = mktime(date('H', $offset), date('i', $offset), 0, date('m'), date('d'), date('Y'));
  1027. // Make the time offset in the past!
  1028. if ($next_time > time())
  1029. {
  1030. $next_time -= 86400;
  1031. }
  1032. // Default we'll jump in hours.
  1033. $applyOffset = 3600;
  1034. // 24 hours = 1 day.
  1035. if ($unit == 'd')
  1036. $applyOffset = 86400;
  1037. // Otherwise a week.
  1038. if ($unit == 'w')
  1039. $applyOffset = 604800;
  1040. $applyOffset *= $regularity;
  1041. // Just add on the offset.
  1042. while ($next_time <= time())
  1043. {
  1044. $next_time += $applyOffset;
  1045. }
  1046. }
  1047. return $next_time;
  1048. }
  1049. /**
  1050. * This loads the bare minimum data to allow us to load language files!
  1051. */
  1052. function loadEssentialThemeData()
  1053. {
  1054. global $settings, $modSettings, $smcFunc, $mbname, $context, $sourcedir, $txt;
  1055. // Get all the default theme variables.
  1056. $result = $smcFunc['db_query']('', '
  1057. SELECT id_theme, variable, value
  1058. FROM {db_prefix}themes
  1059. WHERE id_member = {int:no_member}
  1060. AND id_theme IN (1, {int:theme_guests})',
  1061. array(
  1062. 'no_member' => 0,
  1063. 'theme_guests' => !empty($modSettings['theme_guests']) ? $modSettings['theme_guests'] : 1,
  1064. )
  1065. );
  1066. while ($row = $smcFunc['db_fetch_assoc']($result))
  1067. {
  1068. $settings[$row['variable']] = $row['value'];
  1069. // Is this the default theme?
  1070. if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1')
  1071. $settings['default_' . $row['variable']] = $row['value'];
  1072. }
  1073. $smcFunc['db_free_result']($result);
  1074. // Check we have some directories setup.
  1075. if (empty($settings['template_dirs']))
  1076. {
  1077. $settings['template_dirs'] = array($settings['theme_dir']);
  1078. // Based on theme (if there is one).
  1079. if (!empty($settings['base_theme_dir']))
  1080. $settings['template_dirs'][] = $settings['base_theme_dir'];
  1081. // Lastly the default theme.
  1082. if ($settings['theme_dir'] != $settings['default_theme_dir'])
  1083. $settings['template_dirs'][] = $settings['default_theme_dir'];
  1084. }
  1085. // Assume we want this.
  1086. $context['forum_name'] = $mbname;
  1087. // Check loadLanguage actually exists!
  1088. if (!function_exists('loadLanguage'))
  1089. {
  1090. require_once($sourcedir . '/Load.php');
  1091. require_once($sourcedir . '/Subs.php');
  1092. }
  1093. loadLanguage('index+Modifications');
  1094. // Just in case it wasn't already set elsewhere.
  1095. $context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
  1096. $context['utf8'] = $context['character_set'] === 'UTF-8';
  1097. $context['right_to_left'] = !empty($txt['lang_rtl']);
  1098. }
  1099. /**
  1100. * This retieves data (e.g. last version of SMF) from sm.org
  1101. */
  1102. function scheduled_fetchSMfiles()
  1103. {
  1104. global $sourcedir, $txt, $language, $forum_version, $modSettings, $smcFunc, $context;
  1105. // What files do we want to get
  1106. $request = $smcFunc['db_query']('', '
  1107. SELECT id_file, filename, path, parameters
  1108. FROM {db_prefix}admin_info_files',
  1109. array(
  1110. )
  1111. );
  1112. $js_files = array();
  1113. while ($row = $smcFunc['db_fetch_assoc']($request))
  1114. {
  1115. $js_files[$row['id_file']] = array(
  1116. 'filename' => $row['filename'],
  1117. 'path' => $row['path'],
  1118. 'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode($forum_version)),
  1119. );
  1120. }
  1121. $smcFunc['db_free_result']($request);
  1122. // We're gonna need fetch_web_data() to pull this off.
  1123. require_once($sourcedir . '/Subs-Package.php');
  1124. // Just in case we run into a problem.
  1125. loadEssentialThemeData();
  1126. loadLanguage('Errors', $language, false);
  1127. foreach ($js_files as $ID_FILE => $file)
  1128. {
  1129. // Create the url
  1130. $server = empty($file['path']) || (substr($file['path'], 0, 7) != 'http://' && substr($file['path'], 0, 8) != 'https://') ? 'http://www.simplemachines.org' : '';
  1131. $url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : '');
  1132. // Get the file
  1133. $file_data = fetch_web_data($url);
  1134. // If we got an error - give up - the site might be down. And if we should happen to be coming from elsewhere, let's also make a note of it.
  1135. if ($file_data === false)
  1136. {
  1137. $context['scheduled_errors']['fetchSMfiles'][] = sprintf($txt['st_cannot_retrieve_file'], $url);
  1138. log_error(sprintf($txt['st_cannot_retrieve_file'], $url));
  1139. return false;
  1140. }
  1141. // Save the file to the database.
  1142. $smcFunc['db_query']('substring', '
  1143. UPDATE {db_prefix}admin_info_files
  1144. SET data = SUBSTRING({string:file_data}, 1, 65534)
  1145. WHERE id_file = {int:id_file}',
  1146. array(
  1147. 'id_file' => $ID_FILE,
  1148. 'file_data' => $file_data,
  1149. )
  1150. );
  1151. }
  1152. return true;
  1153. }
  1154. /**
  1155. * Happy birthday!!
  1156. */
  1157. function scheduled_birthdayemails()
  1158. {
  1159. global $modSettings, $sourcedir, $txt, $smcFunc, $txtBirthdayEmails;
  1160. // Need this in order to load the language files.
  1161. loadEssentialThemeData();
  1162. // Going to need this to send the emails.
  1163. require_once($sourcedir . '/Subs-Post.php');
  1164. $greeting = isset($modSettings['birthday_email']) ? $modSettings['birthday_email'] : 'happy_birthday';
  1165. // Get the month and day of today.
  1166. $month = date('n'); // Month without leading zeros.
  1167. $day = date('j'); // Day without leading zeros.
  1168. // So who are the lucky ones? Don't include those who are banned and those who don't want them.
  1169. $result = $smcFunc['db_query']('', '
  1170. SELECT id_member, real_name, lngfile, email_address
  1171. FROM {db_prefix}members
  1172. WHERE is_activated < 10
  1173. AND MONTH(birthdate) = {int:month}
  1174. AND DAYOFMONTH(birthdate) = {int:day}
  1175. AND notify_announcements = {int:notify_announcements}
  1176. AND YEAR(birthdate) > {int:year}',
  1177. array(
  1178. 'notify_announcements' => 1,
  1179. 'year' => 1,
  1180. 'month' => $month,
  1181. 'day' => $day,
  1182. )
  1183. );
  1184. // Group them by languages.
  1185. $birthdays = array();
  1186. while ($row = $smcFunc['db_fetch_assoc']($result))
  1187. {
  1188. if (!isset($birthdays[$row['lngfile']]))
  1189. $birthdays[$row['lngfile']] = array();
  1190. $birthdays[$row['lngfile']][$row['id_member']] = array(
  1191. 'name' => $row['real_name'],
  1192. 'email' => $row['email_address']
  1193. );
  1194. }
  1195. $smcFunc['db_free_result']($result);
  1196. // Send out the greetings!
  1197. foreach ($birthdays as $lang => $recps)
  1198. {
  1199. // We need to do some shuffling to make this work properly.
  1200. loadLanguage('EmailTemplates', $lang);
  1201. $txt['emails']['happy_birthday']['subject'] = $txtBirthdayEmails[$greeting . '_subject'];
  1202. $txt['emails']['happy_birthday']['body'] = $txtBirthdayEmails[$greeting . '_body'];
  1203. foreach ($recps as $recp)
  1204. {
  1205. $replacements = array(
  1206. 'REALNAME' => $recp['name'],
  1207. );
  1208. $emaildata = loadEmailTemplate('happy_birthday', $replacements, $lang, false);
  1209. sendmail($recp['email'], $emaildata['subject'], $emaildata['body'], null, 'birthday', false, 4);
  1210. // Try to stop a timeout, this would be bad...
  1211. @set_time_limit(300);
  1212. if (function_exists('apache_reset_timeout'))
  1213. @apache_reset_timeout();
  1214. }
  1215. }
  1216. // Flush the mail queue, just in case.
  1217. AddMailQueue(true);
  1218. return true;
  1219. }
  1220. /**
  1221. * Weekly maintenance
  1222. */
  1223. function scheduled_weekly_maintenance()
  1224. {
  1225. global $modSettings, $smcFunc;
  1226. // Delete some settings that needn't be set if they are otherwise empty.
  1227. $emptySettings = array(
  1228. 'warning_mute', 'warning_moderate', 'warning_watch', 'warning_show', 'disableCustomPerPage', 'spider_mode', 'spider_group',
  1229. 'paid_currency_code', 'paid_currency_symbol', 'paid_email_to', 'paid_email', 'paid_enabled', 'paypal_email',
  1230. 'search_enable_captcha', 'search_floodcontrol_time', 'show_spider_online',
  1231. );
  1232. $smcFunc['db_query']('', '
  1233. DELETE FROM {db_prefix}settings
  1234. WHERE variable IN ({array_string:setting_list})
  1235. AND (value = {string:zero_value} OR value = {string:blank_value})',
  1236. array(
  1237. 'zero_value' => '0',
  1238. 'blank_value' => '',
  1239. 'setting_list' => $emptySettings,
  1240. )
  1241. );
  1242. // Some settings we never want to keep - they are just there for temporary purposes.
  1243. $deleteAnywaySettings = array(
  1244. 'attachment_full_notified',
  1245. );
  1246. $smcFunc['db_query']('', '
  1247. DELETE FROM {db_prefix}settings
  1248. WHERE variable IN ({array_string:setting_list})',
  1249. array(
  1250. 'setting_list' => $deleteAnywaySettings,
  1251. )
  1252. );
  1253. // Ok should we prune the logs?
  1254. if (!empty($modSettings['pruningOptions']))
  1255. {
  1256. if (!empty($modSettings['pruningOptions']) && strpos($modSettings['pruningOptions'], ',') !== false)
  1257. list ($modSettings['pruneErrorLog'], $modSettings['pruneModLog'], $modSettings['pruneBanLog'], $modSettings['pruneReportLog'], $modSettings['pruneScheduledTaskLog'], $modSettings['pruneSpiderHitLog']) = explode(',', $modSettings['pruningOptions']);
  1258. if (!empty($modSettings['pruneErrorLog']))
  1259. {
  1260. // Figure out when our cutoff time is. 1 day = 86400 seconds.
  1261. $t = time() - $modSettings['pruneErrorLog'] * 86400;
  1262. $smcFunc['db_query']('', '
  1263. DELETE FROM {db_prefix}log_errors
  1264. WHERE log_time < {int:log_time}',
  1265. array(
  1266. 'log_time' => $t,
  1267. )
  1268. );
  1269. }
  1270. if (!empty($modSettings['pruneModLog']))
  1271. {
  1272. // Figure out when our cutoff time is. 1 day = 86400 seconds.
  1273. $t = time() - $modSettings['pruneModLog'] * 86400;
  1274. $smcFunc['db_query']('', '
  1275. DELETE FROM {db_prefix}log_actions
  1276. WHERE log_time < {int:log_time}
  1277. AND id_log = {int:moderation_log}',
  1278. array(
  1279. 'log_time' => $t,
  1280. 'moderation_log' => 1,
  1281. )
  1282. );
  1283. }
  1284. if (!empty($modSettings['pruneBanLog']))
  1285. {
  1286. // Figure out when our cutoff time is. 1 day = 86400 seconds.
  1287. $t = time() - $modSettings['pruneBanLog'] * 86400;
  1288. $smcFunc['db_query']('', '
  1289. DELETE FROM {db_prefix}log_banned
  1290. WHERE log_time < {int:log_time}',
  1291. array(
  1292. 'log_time' => $t,
  1293. )
  1294. );
  1295. }
  1296. if (!empty($modSettings['pruneReportLog']))
  1297. {
  1298. // Figure out when our cutoff time is. 1 day = 86400 seconds.
  1299. $t = time() - $modSettings['pruneReportLog'] * 86400;
  1300. // This one is more complex then the other logs. First we need to figure out which reports are too old.
  1301. $reports = array();
  1302. $result = $smcFunc['db_query']('', '
  1303. SELECT id_report
  1304. FROM {db_prefix}log_reported
  1305. WHERE time_started < {int:time_started}
  1306. AND closed = {int:not_closed}
  1307. AND ignore_all = {int:not_ignored}',
  1308. array(
  1309. 'time_started' => $t,
  1310. 'not_closed' => 0,
  1311. 'not_ignored' => 0,
  1312. )
  1313. );
  1314. while ($row = $smcFunc['db_fetch_row']($result))
  1315. $reports[] = $row[0];
  1316. $smcFunc['db_free_result']($result);
  1317. if (!empty($reports))
  1318. {
  1319. // Now delete the reports...
  1320. $smcFunc['db_query']('', '
  1321. DELETE FROM {db_prefix}log_reported
  1322. WHERE id_report IN ({array_int:report_list})',
  1323. array(
  1324. 'report_list' => $reports,
  1325. )
  1326. );
  1327. // And delete the comments for those reports...
  1328. $smcFunc['db_query']('', '
  1329. DELETE FROM {db_prefix}log_reported_comments
  1330. WHERE id_report IN ({array_int:report_list})',
  1331. array(
  1332. 'report_list' => $reports,
  1333. )
  1334. );
  1335. }
  1336. }
  1337. if (!empty($modSettings['pruneScheduledTaskLog']))
  1338. {
  1339. // Figure out when our cutoff time is. 1 day = 86400 seconds.
  1340. $t = time() - $modSettings['pruneScheduledTaskLog'] * 86400;
  1341. $smcFunc['db_query']('', '
  1342. DELETE FROM {db_prefix}log_scheduled_tasks
  1343. WHERE time_run < {int:time_run}',
  1344. array(
  1345. 'time_run' => $t,
  1346. )
  1347. );
  1348. }
  1349. if (!empty($modSettings['pruneSpiderHitLog']))
  1350. {
  1351. // Figure out when our cutoff time is. 1 day = 86400 seconds.
  1352. $t = time() - $modSettings['pruneSpiderHitLog'] * 86400;
  1353. $smcFunc['db_query']('', '
  1354. DELETE FROM {db_prefix}log_spider_hits
  1355. WHERE log_time < {int:log_time}',
  1356. array(
  1357. 'log_time' => $t,
  1358. )
  1359. );
  1360. }
  1361. }
  1362. // Get rid of any paid subscriptions that were never actioned.
  1363. $smcFunc['db_query']('', '
  1364. DELETE FROM {db_prefix}log_subscribed
  1365. WHERE end_time = {int:no_end_time}
  1366. AND status = {int:not_active}
  1367. AND start_time < {int:start_time}
  1368. AND payments_pending < {int:payments_pending}',
  1369. array(
  1370. 'no_end_time' => 0,
  1371. 'not_active' => 0,
  1372. 'start_time' => time() - 60,
  1373. 'payments_pending' => 1,
  1374. )
  1375. );
  1376. // Some OS's don't seem to clean out their sessions.
  1377. $smcFunc['db_query']('', '
  1378. DELETE FROM {db_prefix}sessions
  1379. WHERE last_update < {int:last_update}',
  1380. array(
  1381. 'last_update' => time() - 86400,
  1382. )
  1383. );
  1384. return true;
  1385. }
  1386. /**
  1387. * Perform the standard checks on expiring/near expiring subscriptions.
  1388. */
  1389. function scheduled_paid_subscriptions()
  1390. {
  1391. global $sourcedir, $scripturl, $smcFunc, $modSettings, $language;
  1392. // Start off by checking for removed subscriptions.
  1393. $request = $smcFunc['db_query']('', '
  1394. SELECT id_subscribe, id_member
  1395. FROM {db_prefix}log_subscribed
  1396. WHERE status = {int:is_active}
  1397. AND end_time < {int:time_now}',
  1398. array(
  1399. 'is_active' => 1,
  1400. 'time_now' => time(),
  1401. )
  1402. );
  1403. while ($row = $smcFunc['db_fetch_assoc']($request))
  1404. {
  1405. require_once($sourcedir . '/ManagePaid.php');
  1406. removeSubscription($row['id_subscribe'], $row['id_member']);
  1407. }
  1408. $smcFunc['db_free_result']($request);
  1409. // Get all those about to expire that have not had a reminder sent.
  1410. $request = $smcFunc['db_query']('', '
  1411. SELECT ls.id_sublog, m.id_member, m.member_name, m.email_address, m.lngfile, s.name, ls.end_time
  1412. FROM {db_prefix}log_subscribed AS ls
  1413. INNER JOIN {db_prefix}subscriptions AS s ON (s.id_subscribe = ls.id_subscribe)
  1414. INNER JOIN {db_prefix}members AS m ON (m.id_member = ls.id_member)
  1415. WHERE ls.status = {int:is_active}
  1416. AND ls.reminder_sent = {int:reminder_sent}
  1417. AND s.reminder > {int:reminder_wanted}
  1418. AND ls.end_time < ({int:time_now} + s.reminder * 86400)',
  1419. array(
  1420. 'is_active' => 1,
  1421. 'reminder_sent' => 0,
  1422. 'reminder_wanted' => 0,
  1423. 'time_now' => time(),
  1424. )
  1425. );
  1426. $subs_reminded = array();
  1427. while ($row = $smcFunc['db_fetch_assoc']($request))
  1428. {
  1429. // If this is the first one load the important bits.
  1430. if (empty($subs_reminded))
  1431. {
  1432. require_once($sourcedir . '/Subs-Post.php');
  1433. // Need the below for loadLanguage to work!
  1434. loadEssentialThemeData();
  1435. }
  1436. $subs_reminded[] = $row['id_sublog'];
  1437. $replacements = array(
  1438. 'PROFILE_LINK' => $scripturl . '?action=profile;area=subscriptions;u=' . $row['id_member'],
  1439. 'REALNAME' => $row['member_name'],
  1440. 'SUBSCRIPTION' => $row['name'],
  1441. 'END_DATE' => strip_tags(timeformat($row['end_time'])),
  1442. );
  1443. $emaildata = loadEmailTemplate('paid_subscription_reminder', $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']);
  1444. // Send the actual email.
  1445. sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, 'paid_sub_remind', false, 2);
  1446. }
  1447. $smcFunc['db_free_result']($request);
  1448. // Mark the reminder as sent.
  1449. if (!empty($subs_reminded))
  1450. $smcFunc['db_query']('', '
  1451. UPDATE {db_prefix}log_subscribed
  1452. SET reminder_sent = {int:reminder_sent}
  1453. WHERE id_sublog IN ({array_int:subscription_list})',
  1454. array(
  1455. 'subscription_list' => $subs_reminded,
  1456. 'reminder_sent' => 1,
  1457. )
  1458. );
  1459. return true;
  1460. }
  1461. /**
  1462. * Check for un-posted attachments is something we can do once in a while :P
  1463. * This function uses opendir cycling through all the attachments
  1464. */
  1465. function scheduled_remove_temp_attachments()
  1466. {
  1467. global $modSettings, $context, $txt;
  1468. // We need to know where this thing is going.
  1469. if (!empty($modSettings['currentAttachmentUploadDir']))
  1470. {
  1471. if (!is_array($modSettings['attachmentUploadDir']))
  1472. $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
  1473. // Just use the current path for temp files.
  1474. $attach_dirs = $modSettings['attachmentUploadDir'];
  1475. }
  1476. else
  1477. {
  1478. $attach_dirs = array($modSettings['attachmentUploadDir']);
  1479. }
  1480. foreach ($attach_dirs as $attach_dir)
  1481. {
  1482. $dir = @opendir($attach_dir);
  1483. if (!$dir)
  1484. {
  1485. loadEssentialThemeData();
  1486. loadLanguage('Post');
  1487. $context['scheduled_errors']['remove_temp_attachments'][] = $txt['cant_access_upload_path'] . ' (' . $attach_dir . ')';
  1488. log_error($txt['cant_access_upload_path'] . ' (' . $attach_dir . ')', 'critical');
  1489. return false;
  1490. }
  1491. while ($file = readdir($dir))
  1492. {
  1493. if ($file == '.' || $file == '..')
  1494. continue;
  1495. if (strpos($file, 'post_tmp_') !== false)
  1496. {
  1497. // Temp file is more than 5 hours old!
  1498. if (filemtime($attach_dir . '/' . $file) < time() - 18000)
  1499. @unlink($attach_dir . '/' . $file);
  1500. }
  1501. }
  1502. closedir($dir);
  1503. }
  1504. return true;
  1505. }
  1506. /**
  1507. * Check for move topic notices that have past their best by date
  1508. */
  1509. function scheduled_remove_topic_redirect()
  1510. {
  1511. global $smcFunc, $sourcedir;
  1512. // init
  1513. $topics = array();
  1514. // We will need this for lanaguage files
  1515. loadEssentialThemeData();
  1516. // Find all of the old MOVE topic notices that were set to expire
  1517. $request = $smcFunc['db_query']('', '
  1518. SELECT id_topic
  1519. FROM {db_prefix}topics
  1520. WHERE redirect_expires <= {int:redirect_expires}
  1521. AND redirect_expires <> 0',
  1522. array(
  1523. 'redirect_expires' => time(),
  1524. )
  1525. );
  1526. while ($row = $smcFunc['db_fetch_row']($request))
  1527. $topics[] = $row[0];
  1528. $smcFunc['db_free_result']($request);
  1529. // Zap, your gone
  1530. if (count($topics) > 0)
  1531. {
  1532. require_once($sourcedir . '/RemoveTopic.php');
  1533. removeTopics($topics, false, true);
  1534. }
  1535. return true;
  1536. }
  1537. /**
  1538. * Check for old drafts and remove them
  1539. */
  1540. function scheduled_remove_old_drafts()
  1541. {
  1542. global $smcFunc, $sourcedir, $modSettings;
  1543. if (empty($modSettings['drafts_keep_days']))
  1544. return true;
  1545. // init
  1546. $drafts = array();
  1547. // We need this for lanaguage items
  1548. loadEssentialThemeData();
  1549. // Find all of the old drafts
  1550. $request = $smcFunc['db_query']('', '
  1551. SELECT id_draft
  1552. FROM {db_prefix}user_drafts
  1553. WHERE poster_time <= {int:poster_time_old}',
  1554. array(
  1555. 'poster_time_old' => time() - (86400 * $modSettings['drafts_keep_days']),
  1556. )
  1557. );
  1558. while ($row = $smcFunc['db_fetch_row']($request))
  1559. $drafts[] = (int) $row[0];
  1560. $smcFunc['db_free_result']($request);
  1561. // If we have old one, remove them
  1562. if (count($drafts) > 0)
  1563. {
  1564. require_once($sourcedir . '/Drafts.php');
  1565. DeleteDraft($drafts, false);
  1566. }
  1567. return true;
  1568. }
  1569. ?>