ManageScheduledTasks.php 16 KB

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