ManageScheduledTasks.php 16 KB

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