ManageScheduledTasks.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. <?php
  2. /**
  3. * This file concerns itself with scheduled tasks management.
  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. * Scheduled tasks management dispatcher. This function checks permissions and delegates
  18. * to the appropriate function based on the sub-action.
  19. * Everything here requires adin_forum permission.
  20. *
  21. * @uses ManageScheduledTasks template file
  22. * @uses ManageScheduledTasks language file
  23. */
  24. function ManageScheduledTasks()
  25. {
  26. global $context, $txt, $modSettings;
  27. isAllowedTo('admin_forum');
  28. loadLanguage('ManageScheduledTasks');
  29. loadTemplate('ManageScheduledTasks');
  30. $subActions = array(
  31. 'taskedit' => 'EditTask',
  32. 'tasklog' => 'TaskLog',
  33. 'tasks' => 'ScheduledTasks',
  34. );
  35. call_integration_hook('integrate_manage_scheduled_tasks', array(&$subActions));
  36. // We need to find what's the action.
  37. if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]))
  38. $context['sub_action'] = $_REQUEST['sa'];
  39. else
  40. $context['sub_action'] = 'tasks';
  41. // Now for the lovely tabs. That we all love.
  42. $context[$context['admin_menu_name']]['tab_data'] = array(
  43. 'title' => $txt['scheduled_tasks_title'],
  44. 'help' => '',
  45. 'description' => $txt['maintain_info'],
  46. 'tabs' => array(
  47. 'tasks' => array(
  48. 'description' => $txt['maintain_tasks_desc'],
  49. ),
  50. 'tasklog' => array(
  51. 'description' => $txt['scheduled_log_desc'],
  52. ),
  53. ),
  54. );
  55. // Call it.
  56. $subActions[$context['sub_action']]();
  57. }
  58. /**
  59. * List all the scheduled task in place on the forum.
  60. *
  61. * @uses ManageScheduledTasks template, view_scheduled_tasks sub-template
  62. */
  63. function ScheduledTasks()
  64. {
  65. global $context, $txt, $sourcedir, $smcFunc, $user_info, $modSettings, $scripturl;
  66. // Mama, setup the template first - cause it's like the most important bit, like pickle in a sandwich.
  67. // ... ironically I don't like pickle. </grudge>
  68. $context['sub_template'] = 'view_scheduled_tasks';
  69. $context['page_title'] = $txt['maintain_tasks'];
  70. // Saving changes?
  71. if (isset($_REQUEST['save']) && isset($_POST['enable_task']))
  72. {
  73. checkSession();
  74. // We'll recalculate the dates at the end!
  75. require_once($sourcedir . '/ScheduledTasks.php');
  76. // Enable and disable as required.
  77. $enablers = array(0);
  78. foreach ($_POST['enable_task'] as $id => $enabled)
  79. if ($enabled)
  80. $enablers[] = (int) $id;
  81. // Do the update!
  82. $smcFunc['db_query']('', '
  83. UPDATE {db_prefix}scheduled_tasks
  84. SET disabled = CASE WHEN id_task IN ({array_int:id_task_enable}) THEN 0 ELSE 1 END',
  85. array(
  86. 'id_task_enable' => $enablers,
  87. )
  88. );
  89. // Pop along...
  90. CalculateNextTrigger();
  91. }
  92. // Want to run any of the tasks?
  93. if (isset($_REQUEST['run']) && isset($_POST['run_task']))
  94. {
  95. // Lets figure out which ones they want to run.
  96. $tasks = array();
  97. foreach ($_POST['run_task'] as $task => $dummy)
  98. $tasks[] = (int) $task;
  99. // Load up the tasks.
  100. $request = $smcFunc['db_query']('', '
  101. SELECT id_task, task
  102. FROM {db_prefix}scheduled_tasks
  103. WHERE id_task IN ({array_int:tasks})
  104. LIMIT ' . count($tasks),
  105. array(
  106. 'tasks' => $tasks,
  107. )
  108. );
  109. // Lets get it on!
  110. require_once($sourcedir . '/ScheduledTasks.php');
  111. ignore_user_abort(true);
  112. while ($row = $smcFunc['db_fetch_assoc']($request))
  113. {
  114. $start_time = microtime();
  115. // The functions got to exist for us to use it.
  116. if (!function_exists('scheduled_' . $row['task']))
  117. continue;
  118. // Try to stop a timeout, this would be bad...
  119. @set_time_limit(300);
  120. if (function_exists('apache_reset_timeout'))
  121. @apache_reset_timeout();
  122. // Do the task...
  123. $completed = call_user_func('scheduled_' . $row['task']);
  124. // Log that we did it ;)
  125. if ($completed)
  126. {
  127. $total_time = round(array_sum(explode(' ', microtime())) - array_sum(explode(' ', $start_time)), 3);
  128. $smcFunc['db_insert']('',
  129. '{db_prefix}log_scheduled_tasks',
  130. array('id_task' => 'int', 'time_run' => 'int', 'time_taken' => 'float'),
  131. array($row['id_task'], time(), $total_time),
  132. array('id_task')
  133. );
  134. }
  135. }
  136. $smcFunc['db_free_result']($request);
  137. redirectexit('action=admin;area=scheduledtasks;done');
  138. }
  139. $listOptions = array(
  140. 'id' => 'scheduled_tasks',
  141. 'title' => $txt['maintain_tasks'],
  142. 'base_href' => $scripturl . '?action=admin;area=scheduledtasks',
  143. 'get_items' => array(
  144. 'function' => 'list_getScheduledTasks',
  145. ),
  146. 'columns' => array(
  147. 'name' => array(
  148. 'header' => array(
  149. 'value' => $txt['scheduled_tasks_name'],
  150. 'style' => 'width: 40%;',
  151. ),
  152. 'data' => array(
  153. 'sprintf' => array(
  154. 'format' => '
  155. <a href="' . $scripturl . '?action=admin;area=scheduledtasks;sa=taskedit;tid=%1$d">%2$s</a><br /><span class="smalltext">%3$s</span>',
  156. 'params' => array(
  157. 'id' => false,
  158. 'name' => false,
  159. 'desc' => false,
  160. ),
  161. ),
  162. ),
  163. ),
  164. 'next_due' => array(
  165. 'header' => array(
  166. 'value' => $txt['scheduled_tasks_next_time'],
  167. ),
  168. 'data' => array(
  169. 'db' => 'next_time',
  170. 'class' => 'smalltext',
  171. ),
  172. ),
  173. 'regularity' => array(
  174. 'header' => array(
  175. 'value' => $txt['scheduled_tasks_regularity'],
  176. ),
  177. 'data' => array(
  178. 'db' => 'regularity',
  179. 'class' => 'smalltext',
  180. ),
  181. ),
  182. 'enabled' => array(
  183. 'header' => array(
  184. 'value' => $txt['scheduled_tasks_enabled'],
  185. 'style' => 'width: 6%;',
  186. 'class' => 'centercol',
  187. ),
  188. 'data' => array(
  189. 'sprintf' => array(
  190. 'format' =>
  191. '<input type="hidden" name="enable_task[%1$d]" id="task_%1$d" value="0" /><input type="checkbox" name="enable_task[%1$d]" id="task_check_%1$d" %2$s class="input_check" />',
  192. 'params' => array(
  193. 'id' => false,
  194. 'checked_state' => false,
  195. ),
  196. ),
  197. 'class' => 'centercol',
  198. ),
  199. ),
  200. 'run_now' => array(
  201. 'header' => array(
  202. 'value' => $txt['scheduled_tasks_run_now'],
  203. 'style' => 'width: 12%;',
  204. 'class' => 'centercol',
  205. ),
  206. 'data' => array(
  207. 'sprintf' => array(
  208. 'format' =>
  209. '<input type="checkbox" name="run_task[%1$d]" id="run_task_%1$d" class="input_check" />',
  210. 'params' => array(
  211. 'id' => false,
  212. ),
  213. ),
  214. 'class' => 'centercol',
  215. ),
  216. ),
  217. ),
  218. 'form' => array(
  219. 'href' => $scripturl . '?action=admin;area=scheduledtasks',
  220. ),
  221. 'additional_rows' => array(
  222. array(
  223. 'position' => 'below_table_data',
  224. 'value' => '
  225. <input type="submit" name="save" value="' . $txt['scheduled_tasks_save_changes'] . '" class="button_submit" />
  226. <input type="submit" name="run" value="' . $txt['scheduled_tasks_run_now'] . '" class="button_submit" />',
  227. ),
  228. array(
  229. 'position' => 'after_title',
  230. 'value' => $txt['scheduled_tasks_time_offset'],
  231. 'class' => 'windowbg2',
  232. ),
  233. ),
  234. );
  235. require_once($sourcedir . '/Subs-List.php');
  236. createList($listOptions);
  237. $context['sub_template'] = 'view_scheduled_tasks';
  238. $context['tasks_were_run'] = isset($_GET['done']);
  239. }
  240. /**
  241. * Callback function for createList() in ScheduledTasks().
  242. *
  243. * @param int $start
  244. * @param int $items_per_page
  245. * @param string $sort
  246. */
  247. function list_getScheduledTasks($start, $items_per_page, $sort)
  248. {
  249. global $smcFunc, $txt, $scripturl;
  250. $request = $smcFunc['db_query']('', '
  251. SELECT id_task, next_time, time_offset, time_regularity, time_unit, disabled, task
  252. FROM {db_prefix}scheduled_tasks',
  253. array(
  254. )
  255. );
  256. $known_tasks = array();
  257. while ($row = $smcFunc['db_fetch_assoc']($request))
  258. {
  259. // Find the next for regularity - don't offset as it's always server time!
  260. $offset = sprintf($txt['scheduled_task_reg_starting'], date('H:i', $row['time_offset']));
  261. $repeating = sprintf($txt['scheduled_task_reg_repeating'], $row['time_regularity'], $txt['scheduled_task_reg_unit_' . $row['time_unit']]);
  262. $known_tasks[] = array(
  263. 'id' => $row['id_task'],
  264. 'function' => $row['task'],
  265. 'name' => isset($txt['scheduled_task_' . $row['task']]) ? $txt['scheduled_task_' . $row['task']] : $row['task'],
  266. 'desc' => isset($txt['scheduled_task_desc_' . $row['task']]) ? $txt['scheduled_task_desc_' . $row['task']] : '',
  267. 'next_time' => $row['disabled'] ? $txt['scheduled_tasks_na'] : timeformat(($row['next_time'] == 0 ? time() : $row['next_time']), true, 'server'),
  268. 'disabled' => $row['disabled'],
  269. 'checked_state' => $row['disabled'] ? '' : 'checked="checked"',
  270. 'regularity' => $offset . ', ' . $repeating,
  271. );
  272. }
  273. $smcFunc['db_free_result']($request);
  274. return $known_tasks;
  275. }
  276. /**
  277. * Function for editing a task.
  278. *
  279. * @uses ManageScheduledTasks template, edit_scheduled_tasks sub-template
  280. */
  281. function EditTask()
  282. {
  283. global $context, $txt, $sourcedir, $smcFunc, $user_info, $modSettings;
  284. // Just set up some lovely context stuff.
  285. $context[$context['admin_menu_name']]['current_subsection'] = 'tasks';
  286. $context['sub_template'] = 'edit_scheduled_tasks';
  287. $context['page_title'] = $txt['scheduled_task_edit'];
  288. $context['server_time'] = timeformat(time(), false, 'server');
  289. // Cleaning...
  290. if (!isset($_GET['tid']))
  291. fatal_lang_error('no_access', false);
  292. $_GET['tid'] = (int) $_GET['tid'];
  293. // Saving?
  294. if (isset($_GET['save']))
  295. {
  296. checkSession();
  297. validateToken('admin-st');
  298. // We'll need this for calculating the next event.
  299. require_once($sourcedir . '/ScheduledTasks.php');
  300. // Do we have a valid offset?
  301. preg_match('~(\d{1,2}):(\d{1,2})~', $_POST['offset'], $matches);
  302. // If a half is empty then assume zero offset!
  303. if (!isset($matches[2]) || $matches[2] > 59)
  304. $matches[2] = 0;
  305. if (!isset($matches[1]) || $matches[1] > 23)
  306. $matches[1] = 0;
  307. // Now the offset is easy; easy peasy - except we need to offset by a few hours...
  308. $offset = $matches[1] * 3600 + $matches[2] * 60 - date('Z');
  309. // The other time bits are simple!
  310. $interval = max((int) $_POST['regularity'], 1);
  311. $unit = in_array(substr($_POST['unit'], 0, 1), array('m', 'h', 'd', 'w')) ? substr($_POST['unit'], 0, 1) : 'd';
  312. // Don't allow one minute intervals.
  313. if ($interval == 1 && $unit == 'm')
  314. $interval = 2;
  315. // Is it disabled?
  316. $disabled = !isset($_POST['enabled']) ? 1 : 0;
  317. // Do the update!
  318. $smcFunc['db_query']('', '
  319. UPDATE {db_prefix}scheduled_tasks
  320. SET disabled = {int:disabled}, time_offset = {int:time_offset}, time_unit = {string:time_unit},
  321. time_regularity = {int:time_regularity}
  322. WHERE id_task = {int:id_task}',
  323. array(
  324. 'disabled' => $disabled,
  325. 'time_offset' => $offset,
  326. 'time_regularity' => $interval,
  327. 'id_task' => $_GET['tid'],
  328. 'time_unit' => $unit,
  329. )
  330. );
  331. // Check the next event.
  332. CalculateNextTrigger($_GET['tid'], true);
  333. // Return to the main list.
  334. redirectexit('action=admin;area=scheduledtasks');
  335. }
  336. // Load the task, understand? Que? Que?
  337. $request = $smcFunc['db_query']('', '
  338. SELECT id_task, next_time, time_offset, time_regularity, time_unit, disabled, task
  339. FROM {db_prefix}scheduled_tasks
  340. WHERE id_task = {int:id_task}',
  341. array(
  342. 'id_task' => $_GET['tid'],
  343. )
  344. );
  345. // Should never, ever, happen!
  346. if ($smcFunc['db_num_rows']($request) == 0)
  347. fatal_lang_error('no_access', false);
  348. while ($row = $smcFunc['db_fetch_assoc']($request))
  349. {
  350. $context['task'] = array(
  351. 'id' => $row['id_task'],
  352. 'function' => $row['task'],
  353. 'name' => isset($txt['scheduled_task_' . $row['task']]) ? $txt['scheduled_task_' . $row['task']] : $row['task'],
  354. 'desc' => isset($txt['scheduled_task_desc_' . $row['task']]) ? $txt['scheduled_task_desc_' . $row['task']] : '',
  355. 'next_time' => $row['disabled'] ? $txt['scheduled_tasks_na'] : timeformat($row['next_time'] == 0 ? time() : $row['next_time'], true, 'server'),
  356. 'disabled' => $row['disabled'],
  357. 'offset' => $row['time_offset'],
  358. 'regularity' => $row['time_regularity'],
  359. 'offset_formatted' => date('H:i', $row['time_offset']),
  360. 'unit' => $row['time_unit'],
  361. );
  362. }
  363. $smcFunc['db_free_result']($request);
  364. createToken('admin-st');
  365. }
  366. /**
  367. * Show the log of all tasks that have taken place.
  368. *
  369. * @uses ManageScheduledTasks language file
  370. */
  371. function TaskLog()
  372. {
  373. global $scripturl, $context, $txt, $smcFunc, $sourcedir;
  374. // Lets load the language just incase we are outside the Scheduled area.
  375. loadLanguage('ManageScheduledTasks');
  376. // Empty the log?
  377. if (!empty($_POST['removeAll']))
  378. {
  379. checkSession();
  380. validateToken('admin-tl');
  381. $smcFunc['db_query']('truncate_table', '
  382. TRUNCATE {db_prefix}log_scheduled_tasks',
  383. array(
  384. )
  385. );
  386. }
  387. // Setup the list.
  388. $listOptions = array(
  389. 'id' => 'task_log',
  390. 'items_per_page' => 30,
  391. 'title' => $txt['scheduled_log'],
  392. 'no_items_label' => $txt['scheduled_log_empty'],
  393. 'base_href' => $context['admin_area'] == 'scheduledtasks' ? $scripturl . '?action=admin;area=scheduledtasks;sa=tasklog' : $scripturl . '?action=admin;area=logs;sa=tasklog',
  394. 'default_sort_col' => 'date',
  395. 'get_items' => array(
  396. 'function' => 'list_getTaskLogEntries',
  397. ),
  398. 'get_count' => array(
  399. 'function' => 'list_getNumTaskLogEntries',
  400. ),
  401. 'columns' => array(
  402. 'name' => array(
  403. 'header' => array(
  404. 'value' => $txt['scheduled_tasks_name'],
  405. ),
  406. 'data' => array(
  407. 'db' => 'name'
  408. ),
  409. ),
  410. 'date' => array(
  411. 'header' => array(
  412. 'value' => $txt['scheduled_log_time_run'],
  413. ),
  414. 'data' => array(
  415. 'function' => create_function('$rowData', '
  416. return timeformat($rowData[\'time_run\'], true);
  417. '),
  418. ),
  419. 'sort' => array(
  420. 'default' => 'lst.id_log DESC',
  421. 'reverse' => 'lst.id_log',
  422. ),
  423. ),
  424. 'time_taken' => array(
  425. 'header' => array(
  426. 'value' => $txt['scheduled_log_time_taken'],
  427. ),
  428. 'data' => array(
  429. 'sprintf' => array(
  430. 'format' => $txt['scheduled_log_time_taken_seconds'],
  431. 'params' => array(
  432. 'time_taken' => false,
  433. ),
  434. ),
  435. ),
  436. 'sort' => array(
  437. 'default' => 'lst.time_taken',
  438. 'reverse' => 'lst.time_taken DESC',
  439. ),
  440. ),
  441. ),
  442. 'form' => array(
  443. 'href' => $context['admin_area'] == 'scheduledtasks' ? $scripturl . '?action=admin;area=scheduledtasks;sa=tasklog' : $scripturl . '?action=admin;area=logs;sa=tasklog',
  444. 'token' => 'admin-tl',
  445. ),
  446. 'additional_rows' => array(
  447. array(
  448. 'position' => 'below_table_data',
  449. 'value' => '
  450. <input type="submit" name="removeAll" value="' . $txt['scheduled_log_empty_log'] . '" onclick="return confirm(\'' . $txt['scheduled_log_empty_log_confirm'] . '\');" class="button_submit" />',
  451. ),
  452. array(
  453. 'position' => 'after_title',
  454. 'value' => $txt['scheduled_tasks_time_offset'],
  455. 'class' => 'windowbg2',
  456. ),
  457. ),
  458. );
  459. createToken('admin-tl');
  460. require_once($sourcedir . '/Subs-List.php');
  461. createList($listOptions);
  462. $context['sub_template'] = 'show_list';
  463. $context['default_list'] = 'task_log';
  464. // Make it all look tify.
  465. $context[$context['admin_menu_name']]['current_subsection'] = 'tasklog';
  466. $context['page_title'] = $txt['scheduled_log'];
  467. }
  468. /**
  469. * Callback function for createList() in TaskLog().
  470. *
  471. * @param int $start
  472. * @param int $items_per_page
  473. * @param string $sort
  474. */
  475. function list_getTaskLogEntries($start, $items_per_page, $sort)
  476. {
  477. global $smcFunc, $txt;
  478. $request = $smcFunc['db_query']('', '
  479. SELECT lst.id_log, lst.id_task, lst.time_run, lst.time_taken, st.task
  480. FROM {db_prefix}log_scheduled_tasks AS lst
  481. INNER JOIN {db_prefix}scheduled_tasks AS st ON (st.id_task = lst.id_task)
  482. ORDER BY ' . $sort . '
  483. LIMIT ' . $start . ', ' . $items_per_page,
  484. array(
  485. )
  486. );
  487. $log_entries = array();
  488. while ($row = $smcFunc['db_fetch_assoc']($request))
  489. $log_entries[] = array(
  490. 'id' => $row['id_log'],
  491. 'name' => isset($txt['scheduled_task_' . $row['task']]) ? $txt['scheduled_task_' . $row['task']] : $row['task'],
  492. 'time_run' => $row['time_run'],
  493. 'time_taken' => $row['time_taken'],
  494. );
  495. $smcFunc['db_free_result']($request);
  496. return $log_entries;
  497. }
  498. /**
  499. * Callback function for createList() in TaskLog().
  500. */
  501. function list_getNumTaskLogEntries()
  502. {
  503. global $smcFunc;
  504. $request = $smcFunc['db_query']('', '
  505. SELECT COUNT(*)
  506. FROM {db_prefix}log_scheduled_tasks',
  507. array(
  508. )
  509. );
  510. list ($num_entries) = $smcFunc['db_fetch_row']($request);
  511. $smcFunc['db_free_result']($request);
  512. return $num_entries;
  513. }
  514. ?>