ManageScheduledTasks.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  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 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. * 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;
  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, $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. // If we had any errors, push them to session so we can pick them up next time to tell the user.
  138. if (!empty($context['scheduled_errors']))
  139. $_SESSION['st_error'] = $context['scheduled_errors'];
  140. redirectexit('action=admin;area=scheduledtasks;done');
  141. }
  142. if (isset($_SESSION['st_error']))
  143. {
  144. $context['scheduled_errors'] = $_SESSION['st_error'];
  145. unset ($_SESSION['st_error']);
  146. }
  147. $listOptions = array(
  148. 'id' => 'scheduled_tasks',
  149. 'title' => $txt['maintain_tasks'],
  150. 'base_href' => $scripturl . '?action=admin;area=scheduledtasks',
  151. 'get_items' => array(
  152. 'function' => 'list_getScheduledTasks',
  153. ),
  154. 'columns' => array(
  155. 'name' => array(
  156. 'header' => array(
  157. 'value' => $txt['scheduled_tasks_name'],
  158. 'style' => 'width: 40%;',
  159. ),
  160. 'data' => array(
  161. 'sprintf' => array(
  162. 'format' => '
  163. <a href="' . $scripturl . '?action=admin;area=scheduledtasks;sa=taskedit;tid=%1$d">%2$s</a><br /><span class="smalltext">%3$s</span>',
  164. 'params' => array(
  165. 'id' => false,
  166. 'name' => false,
  167. 'desc' => false,
  168. ),
  169. ),
  170. ),
  171. ),
  172. 'next_due' => array(
  173. 'header' => array(
  174. 'value' => $txt['scheduled_tasks_next_time'],
  175. ),
  176. 'data' => array(
  177. 'db' => 'next_time',
  178. 'class' => 'smalltext',
  179. ),
  180. ),
  181. 'regularity' => array(
  182. 'header' => array(
  183. 'value' => $txt['scheduled_tasks_regularity'],
  184. ),
  185. 'data' => array(
  186. 'db' => 'regularity',
  187. 'class' => 'smalltext',
  188. ),
  189. ),
  190. 'enabled' => array(
  191. 'header' => array(
  192. 'value' => $txt['scheduled_tasks_enabled'],
  193. 'style' => 'width: 6%;',
  194. 'class' => 'centercol',
  195. ),
  196. 'data' => array(
  197. 'sprintf' => array(
  198. 'format' =>
  199. '<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" />',
  200. 'params' => array(
  201. 'id' => false,
  202. 'checked_state' => false,
  203. ),
  204. ),
  205. 'class' => 'centercol',
  206. ),
  207. ),
  208. 'run_now' => array(
  209. 'header' => array(
  210. 'value' => $txt['scheduled_tasks_run_now'],
  211. 'style' => 'width: 12%;',
  212. 'class' => 'centercol',
  213. ),
  214. 'data' => array(
  215. 'sprintf' => array(
  216. 'format' =>
  217. '<input type="checkbox" name="run_task[%1$d]" id="run_task_%1$d" class="input_check" />',
  218. 'params' => array(
  219. 'id' => false,
  220. ),
  221. ),
  222. 'class' => 'centercol',
  223. ),
  224. ),
  225. ),
  226. 'form' => array(
  227. 'href' => $scripturl . '?action=admin;area=scheduledtasks',
  228. ),
  229. 'additional_rows' => array(
  230. array(
  231. 'position' => 'below_table_data',
  232. 'value' => '
  233. <input type="submit" name="save" value="' . $txt['scheduled_tasks_save_changes'] . '" class="button_submit" />
  234. <input type="submit" name="run" value="' . $txt['scheduled_tasks_run_now'] . '" class="button_submit" />',
  235. ),
  236. array(
  237. 'position' => 'after_title',
  238. 'value' => $txt['scheduled_tasks_time_offset'],
  239. 'class' => 'windowbg2',
  240. ),
  241. ),
  242. );
  243. require_once($sourcedir . '/Subs-List.php');
  244. createList($listOptions);
  245. $context['sub_template'] = 'view_scheduled_tasks';
  246. $context['tasks_were_run'] = isset($_GET['done']);
  247. }
  248. /**
  249. * Callback function for createList() in ScheduledTasks().
  250. *
  251. * @param int $start
  252. * @param int $items_per_page
  253. * @param string $sort
  254. */
  255. function list_getScheduledTasks($start, $items_per_page, $sort)
  256. {
  257. global $smcFunc, $txt, $scripturl;
  258. $request = $smcFunc['db_query']('', '
  259. SELECT id_task, next_time, time_offset, time_regularity, time_unit, disabled, task
  260. FROM {db_prefix}scheduled_tasks',
  261. array(
  262. )
  263. );
  264. $known_tasks = array();
  265. while ($row = $smcFunc['db_fetch_assoc']($request))
  266. {
  267. // Find the next for regularity - don't offset as it's always server time!
  268. $offset = sprintf($txt['scheduled_task_reg_starting'], date('H:i', $row['time_offset']));
  269. $repeating = sprintf($txt['scheduled_task_reg_repeating'], $row['time_regularity'], $txt['scheduled_task_reg_unit_' . $row['time_unit']]);
  270. $known_tasks[] = array(
  271. 'id' => $row['id_task'],
  272. 'function' => $row['task'],
  273. 'name' => isset($txt['scheduled_task_' . $row['task']]) ? $txt['scheduled_task_' . $row['task']] : $row['task'],
  274. 'desc' => isset($txt['scheduled_task_desc_' . $row['task']]) ? $txt['scheduled_task_desc_' . $row['task']] : '',
  275. 'next_time' => $row['disabled'] ? $txt['scheduled_tasks_na'] : timeformat(($row['next_time'] == 0 ? time() : $row['next_time']), true, 'server'),
  276. 'disabled' => $row['disabled'],
  277. 'checked_state' => $row['disabled'] ? '' : 'checked="checked"',
  278. 'regularity' => $offset . ', ' . $repeating,
  279. );
  280. }
  281. $smcFunc['db_free_result']($request);
  282. return $known_tasks;
  283. }
  284. /**
  285. * Function for editing a task.
  286. *
  287. * @uses ManageScheduledTasks template, edit_scheduled_tasks sub-template
  288. */
  289. function EditTask()
  290. {
  291. global $context, $txt, $sourcedir, $smcFunc, $user_info;
  292. // Just set up some lovely context stuff.
  293. $context[$context['admin_menu_name']]['current_subsection'] = 'tasks';
  294. $context['sub_template'] = 'edit_scheduled_tasks';
  295. $context['page_title'] = $txt['scheduled_task_edit'];
  296. $context['server_time'] = timeformat(time(), false, 'server');
  297. // Cleaning...
  298. if (!isset($_GET['tid']))
  299. fatal_lang_error('no_access', false);
  300. $_GET['tid'] = (int) $_GET['tid'];
  301. // Saving?
  302. if (isset($_GET['save']))
  303. {
  304. checkSession();
  305. validateToken('admin-st');
  306. // We'll need this for calculating the next event.
  307. require_once($sourcedir . '/ScheduledTasks.php');
  308. // Do we have a valid offset?
  309. preg_match('~(\d{1,2}):(\d{1,2})~', $_POST['offset'], $matches);
  310. // If a half is empty then assume zero offset!
  311. if (!isset($matches[2]) || $matches[2] > 59)
  312. $matches[2] = 0;
  313. if (!isset($matches[1]) || $matches[1] > 23)
  314. $matches[1] = 0;
  315. // Now the offset is easy; easy peasy - except we need to offset by a few hours...
  316. $offset = $matches[1] * 3600 + $matches[2] * 60 - date('Z');
  317. // The other time bits are simple!
  318. $interval = max((int) $_POST['regularity'], 1);
  319. $unit = in_array(substr($_POST['unit'], 0, 1), array('m', 'h', 'd', 'w')) ? substr($_POST['unit'], 0, 1) : 'd';
  320. // Don't allow one minute intervals.
  321. if ($interval == 1 && $unit == 'm')
  322. $interval = 2;
  323. // Is it disabled?
  324. $disabled = !isset($_POST['enabled']) ? 1 : 0;
  325. // Do the update!
  326. $smcFunc['db_query']('', '
  327. UPDATE {db_prefix}scheduled_tasks
  328. SET disabled = {int:disabled}, time_offset = {int:time_offset}, time_unit = {string:time_unit},
  329. time_regularity = {int:time_regularity}
  330. WHERE id_task = {int:id_task}',
  331. array(
  332. 'disabled' => $disabled,
  333. 'time_offset' => $offset,
  334. 'time_regularity' => $interval,
  335. 'id_task' => $_GET['tid'],
  336. 'time_unit' => $unit,
  337. )
  338. );
  339. // Check the next event.
  340. CalculateNextTrigger($_GET['tid'], true);
  341. // Return to the main list.
  342. redirectexit('action=admin;area=scheduledtasks');
  343. }
  344. // Load the task, understand? Que? Que?
  345. $request = $smcFunc['db_query']('', '
  346. SELECT id_task, next_time, time_offset, time_regularity, time_unit, disabled, task
  347. FROM {db_prefix}scheduled_tasks
  348. WHERE id_task = {int:id_task}',
  349. array(
  350. 'id_task' => $_GET['tid'],
  351. )
  352. );
  353. // Should never, ever, happen!
  354. if ($smcFunc['db_num_rows']($request) == 0)
  355. fatal_lang_error('no_access', false);
  356. while ($row = $smcFunc['db_fetch_assoc']($request))
  357. {
  358. $context['task'] = array(
  359. 'id' => $row['id_task'],
  360. 'function' => $row['task'],
  361. 'name' => isset($txt['scheduled_task_' . $row['task']]) ? $txt['scheduled_task_' . $row['task']] : $row['task'],
  362. 'desc' => isset($txt['scheduled_task_desc_' . $row['task']]) ? $txt['scheduled_task_desc_' . $row['task']] : '',
  363. 'next_time' => $row['disabled'] ? $txt['scheduled_tasks_na'] : timeformat($row['next_time'] == 0 ? time() : $row['next_time'], true, 'server'),
  364. 'disabled' => $row['disabled'],
  365. 'offset' => $row['time_offset'],
  366. 'regularity' => $row['time_regularity'],
  367. 'offset_formatted' => date('H:i', $row['time_offset']),
  368. 'unit' => $row['time_unit'],
  369. );
  370. }
  371. $smcFunc['db_free_result']($request);
  372. createToken('admin-st');
  373. }
  374. /**
  375. * Show the log of all tasks that have taken place.
  376. *
  377. * @uses ManageScheduledTasks language file
  378. */
  379. function TaskLog()
  380. {
  381. global $scripturl, $context, $txt, $smcFunc, $sourcedir;
  382. // Lets load the language just incase we are outside the Scheduled area.
  383. loadLanguage('ManageScheduledTasks');
  384. // Empty the log?
  385. if (!empty($_POST['removeAll']))
  386. {
  387. checkSession();
  388. validateToken('admin-tl');
  389. $smcFunc['db_query']('truncate_table', '
  390. TRUNCATE {db_prefix}log_scheduled_tasks',
  391. array(
  392. )
  393. );
  394. }
  395. // Setup the list.
  396. $listOptions = array(
  397. 'id' => 'task_log',
  398. 'items_per_page' => 30,
  399. 'title' => $txt['scheduled_log'],
  400. 'no_items_label' => $txt['scheduled_log_empty'],
  401. 'base_href' => $context['admin_area'] == 'scheduledtasks' ? $scripturl . '?action=admin;area=scheduledtasks;sa=tasklog' : $scripturl . '?action=admin;area=logs;sa=tasklog',
  402. 'default_sort_col' => 'date',
  403. 'get_items' => array(
  404. 'function' => 'list_getTaskLogEntries',
  405. ),
  406. 'get_count' => array(
  407. 'function' => 'list_getNumTaskLogEntries',
  408. ),
  409. 'columns' => array(
  410. 'name' => array(
  411. 'header' => array(
  412. 'value' => $txt['scheduled_tasks_name'],
  413. ),
  414. 'data' => array(
  415. 'db' => 'name'
  416. ),
  417. ),
  418. 'date' => array(
  419. 'header' => array(
  420. 'value' => $txt['scheduled_log_time_run'],
  421. ),
  422. 'data' => array(
  423. 'function' => create_function('$rowData', '
  424. return timeformat($rowData[\'time_run\'], true);
  425. '),
  426. ),
  427. 'sort' => array(
  428. 'default' => 'lst.id_log DESC',
  429. 'reverse' => 'lst.id_log',
  430. ),
  431. ),
  432. 'time_taken' => array(
  433. 'header' => array(
  434. 'value' => $txt['scheduled_log_time_taken'],
  435. ),
  436. 'data' => array(
  437. 'sprintf' => array(
  438. 'format' => $txt['scheduled_log_time_taken_seconds'],
  439. 'params' => array(
  440. 'time_taken' => false,
  441. ),
  442. ),
  443. ),
  444. 'sort' => array(
  445. 'default' => 'lst.time_taken',
  446. 'reverse' => 'lst.time_taken DESC',
  447. ),
  448. ),
  449. ),
  450. 'form' => array(
  451. 'href' => $context['admin_area'] == 'scheduledtasks' ? $scripturl . '?action=admin;area=scheduledtasks;sa=tasklog' : $scripturl . '?action=admin;area=logs;sa=tasklog',
  452. 'token' => 'admin-tl',
  453. ),
  454. 'additional_rows' => array(
  455. array(
  456. 'position' => 'below_table_data',
  457. 'value' => '
  458. <input type="submit" name="removeAll" value="' . $txt['scheduled_log_empty_log'] . '" onclick="return confirm(\'' . $txt['scheduled_log_empty_log_confirm'] . '\');" class="button_submit" />',
  459. ),
  460. array(
  461. 'position' => 'after_title',
  462. 'value' => $txt['scheduled_tasks_time_offset'],
  463. 'class' => 'windowbg2',
  464. ),
  465. ),
  466. );
  467. createToken('admin-tl');
  468. require_once($sourcedir . '/Subs-List.php');
  469. createList($listOptions);
  470. $context['sub_template'] = 'show_list';
  471. $context['default_list'] = 'task_log';
  472. // Make it all look tify.
  473. $context[$context['admin_menu_name']]['current_subsection'] = 'tasklog';
  474. $context['page_title'] = $txt['scheduled_log'];
  475. }
  476. /**
  477. * Callback function for createList() in TaskLog().
  478. *
  479. * @param int $start
  480. * @param int $items_per_page
  481. * @param string $sort
  482. */
  483. function list_getTaskLogEntries($start, $items_per_page, $sort)
  484. {
  485. global $smcFunc, $txt;
  486. $request = $smcFunc['db_query']('', '
  487. SELECT lst.id_log, lst.id_task, lst.time_run, lst.time_taken, st.task
  488. FROM {db_prefix}log_scheduled_tasks AS lst
  489. INNER JOIN {db_prefix}scheduled_tasks AS st ON (st.id_task = lst.id_task)
  490. ORDER BY ' . $sort . '
  491. LIMIT ' . $start . ', ' . $items_per_page,
  492. array(
  493. )
  494. );
  495. $log_entries = array();
  496. while ($row = $smcFunc['db_fetch_assoc']($request))
  497. $log_entries[] = array(
  498. 'id' => $row['id_log'],
  499. 'name' => isset($txt['scheduled_task_' . $row['task']]) ? $txt['scheduled_task_' . $row['task']] : $row['task'],
  500. 'time_run' => $row['time_run'],
  501. 'time_taken' => $row['time_taken'],
  502. );
  503. $smcFunc['db_free_result']($request);
  504. return $log_entries;
  505. }
  506. /**
  507. * Callback function for createList() in TaskLog().
  508. */
  509. function list_getNumTaskLogEntries()
  510. {
  511. global $smcFunc;
  512. $request = $smcFunc['db_query']('', '
  513. SELECT COUNT(*)
  514. FROM {db_prefix}log_scheduled_tasks',
  515. array(
  516. )
  517. );
  518. list ($num_entries) = $smcFunc['db_fetch_row']($request);
  519. $smcFunc['db_free_result']($request);
  520. return $num_entries;
  521. }
  522. ?>