Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
cron_job.inc
1 <?php
18 require_once SQ_INCLUDE_PATH.'/asset.inc';
19 
20 define('SQ_CRON_JOB_ERROR', 1);// run()/_exec() failed
21 define('SQ_CRON_JOB_COMPLETED', 2);// run()/_exec() successful and complete
22 define('SQ_CRON_JOB_NOT_COMPLETE', 4);// run()/_exec() successful and but still more to do
23 define('SQ_CRON_JOB_REMOVE', 8);// cron job can be removed from cron manager
24 define('SQ_CRON_JOB_RETRY', 16);// cron job requests to be retried (only valid with ERROR and NOT_COMPLETE)
25 
26 
39 class Cron_Job extends Asset
40 {
41 
42 
49  function __construct($assetid=0)
50  {
51  parent::__construct($assetid);
52 
53  }//end constructor
54 
55 
66  protected function _preCreateCheck(Array &$link)
67  {
68  if (!parent::_preCreateCheck($link)) return FALSE;
69 
70  if (!$this->attr('type') || !$this->attr('when')) {
71  trigger_localised_error('CRON0002', E_USER_ERROR);
72  return FALSE;
73  }
74 
75  return TRUE;
76 
77  }//end _preCreateCheck()
78 
79 
87  public function _getAllowedLinks()
88  {
89  return Array(
90  SQ_LINK_TYPE_1 => Array(),
91  SQ_LINK_TYPE_2 => Array(),
92  SQ_LINK_TYPE_3 => Array(),
93  SQ_LINK_NOTICE => Array('user' => Array('card' => 1)),
94  );
95 
96  }//end _getAllowedLinks()
97 
98 
105  public function canClone()
106  {
107  return FALSE;
108 
109  }//end canClone()
110 
111 
122  public function morph($new_type_code)
123  {
124  trigger_localised_error('CRON0024', E_USER_WARNING, $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name'));
125  return FALSE;
126 
127  }//end morph()
128 
129 
141  public function moveLinkPos($linkid, $sort_order=-1)
142  {
143  trigger_localised_error('CRON0041', E_USER_WARNING, $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name'));
144  return FALSE;
145 
146  }//end moveLinkPos()
147 
148 
158  public function canForceablyAcquireLock($lock_type)
159  {
160  return FALSE;
161 
162  }//end canForceablyAcquireLock()
163 
164 
174  public function setAttrValue($name, $value)
175  {
176  if ($name == 'when') {
177 
178  $value = (string) $value;
179  if ($this->attr('type') != 'one_off' && substr($value, 0, 2) == 'OO') {
180  trigger_localised_error('CRON0009', E_USER_NOTICE);
181  return FALSE;
182  }//end if
183  if ($this->attr('type') == 'one_off' && substr($value, 0, 2) != 'OO') {
184  trigger_localised_error('CRON0043', E_USER_NOTICE);
185  return FALSE;
186  }// end if
187 
188  /*
189  time interval can only be set in a repeating cron job
190  when str format: "TI=nextrun timestamp,time interval,time interval unit
191  time in seconds between each run = time interval * time interval unit
192  for example, repeating time interval cron job that runs
193  1. every 15 minutes
194  TI=1147416120,15,60
195  2. every hour
196  TI=1147416120,1,3600
197  */
198  if ($this->attr('type') != 'repeating' && substr($value, 0, 2) == 'TI') {
199  trigger_localised_error('CRON0045', E_USER_NOTICE);
200  return FALSE;
201  }//end if
202 
203  // if this is a one off job
204  // then we need to make sure that it is happening on or before the
205  // next time that the cron job is getting run
206  if ($this->attr('type') == 'one_off') {
207 
208  $when_str = $value;
209 
210  $cron_mgr = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('cron_manager');
211  if (is_null($cron_mgr)) return FALSE;
212 
213  // is there a duration set? if so, recalculate the when time
214  if (strpos($when_str, '!')) {
215  // user chose a duration instead of a fixed date, so lets work out what that is
216  $now = time();
217  $duration = substr($value, strpos($value, '!') + 2);
218  $period = substr($value, strpos($value, '!') + 1, 1);
219 
220  switch ($period) {
221  case 'i':
222  $then = $now + $duration*60;
223  break;
224  case 'h':
225  $then = $now + $duration*3600;
226  break;
227  case 'd':
228  $then = $now + $duration*86400;
229  break;
230  case 'w':
231  $then = $now + $duration*604800;
232  break;
233  case 'm':
234  $then = strtotime('+'.$duration.' months');
235  break;
236  case 'y':
237  $then = strtotime('+'.$duration.' years');
238  break;
239  }//end switch
240 
241  // If the job is scheduled to run immediately, make sure the current time is not as same as
242  // the Cron Manager's last run, otherwise this job will never be picked (see bug #5943)
243  if ($now == $then && $cron_mgr->attr('last_run') == strtotime(date('Y-m-d H:i', $now))) {
244  // Push it to the next Cron run
245  $then += $cron_mgr->attr('refresh_time');
246  }
247 
248  $when_str = 'OO=';
249 
250  foreach (Array('year' => 'Y', 'month' => 'm', 'day' => 'd', 'hour' => 'H', 'minute' => 'i') as $field => $format_string) {
251  $fields[$field] = date($format_string, $then);
252  switch ($field) {
253  case 'year' :
254  $when_str .= $fields[$field].'-';
255  break;
256  case 'month' :
257  $when_str .= $fields[$field].'-';
258  break;
259  case 'day' :
260  $when_str .= $fields[$field].' ';
261  break;
262  case 'hour' :
263  $when_str .= $fields[$field].':';
264  break;
265  case 'minute' :
266  $minutes = date($format_string, $then);
267  $interval = $cron_mgr->attr('refresh_time') / 60;
268  while ($minutes % $interval != 0) {
269  $minutes++;
270  }
271  // we have removed an "if" block from here
272  // we need not worry about the minutes greater than 60
273  // PHP mktime() will take care if we pass minutes like
274  // 60 or greater, 11:60 and 12:00 OR 11:66 and 12:06
275  // will give us same results as timestamp outta mktime
276  $fields[$field] = $minutes;
277  $when_str .= $fields[$field];
278  break;
279  }// end switch
280 
281  }//end foreach
282 
283  }//end if
284 
285  $when = Cron_Job::getWhenArr($when_str);
286 
287  // the next run time is stored in the encoded when str, for a repeating time interval cron job (TI)
288  $when_ts = mktime($when['hour'], $when['minute'], 0, $when['month'], $when['day'], $when['year']);
289  if ($this->attr('type') == 'repeating' && substr($value, 0, 2) == 'TI') {
290  $when_ts = $when['duration'];
291  }
292 
293  if ($when_ts < (int) $cron_mgr->attr('last_run')) {
294  require_once SQ_FUDGE_PATH.'/general/datetime.inc';
295  trigger_localised_error('CRON0042', E_USER_WARNING, $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name'), easy_datetime((int) $cron_mgr->attr('last_run')));
296  return FALSE;
297  }//end if
298 
299  // instead of using $when_str, convert back the date
300  // from when_ts this will ensure us that the date if
301  // passed something like 11:64 to mktime will be
302  // converted back to 12:04 below
303  // for proper displaying on backend
304  $value = 'OO='.date('Y-m-d H:i', $when_ts);
305 
306  }//end if
307 
308  // Make sure exact date/times have the right precision, because later DB queries depend on this
309  if (0 === strpos($value, 'OO=')) {
310  $value = substr($value, 0, 19);
311  }
312 
313  }//end if
314 
315 
316  return parent::setAttrValue($name, $value);
317 
318  }//end setAttrValue()
319 
320 
329  public function canDelete()
330  {
331  // read-only?
332  if ($this->attr('read_only')) return FALSE;
333 
334  $user = $this->getRunningUser();
335  if (!is_null($user) && $GLOBALS['SQ_SYSTEM']->currentUser($user)) {
336  return TRUE;
337  }
338 
339  // if this is to be run as the root user, it can only be deleted by the root user
340  if (!is_null($user) && $GLOBALS['SQ_SYSTEM']->userRoot($user) && !$GLOBALS['SQ_SYSTEM']->userRoot()) {
341  return FALSE;
342  }
343 
344  // else root user or sys admins can delete
345  if ($GLOBALS['SQ_SYSTEM']->userRoot() || $GLOBALS['SQ_SYSTEM']->userSystemAdmin()) {
346  return TRUE;
347  }
348 
349  return FALSE;
350 
351  }//end canDelete()
352 
353 
360  public function getRunningUser()
361  {
362  $null = NULL; // because we need to return by ref
363  $userid = $this->attr('running_as');
364  if (!$userid) return $null;
365  $user = $GLOBALS['SQ_SYSTEM']->am->getAsset($userid, '', TRUE);
366  if (is_null($user)) return $null;
367  return $user;
368 
369  }//end getRunningUser()
370 
371 
381  public static function whenTypeName($type)
382  {
383  switch ($type) {
384  case 'ET' : return translate('every_time');
385  case 'OO' : return translate('one_off');
386  case 'HR' : return translate('hourly');
387  case 'DL' : return translate('daily');
388  case 'WK' : return translate('weekly');
389  case 'MT' : return translate('monthly');
390  case 'YR' : return translate('yearly');
391  case 'TI' : return translate('repeating');
392  default :
393  trigger_localised_error('CRON0005', $type, E_USER_NOTICE);
394  return '';
395  }
396 
397  }//end whenTypeName()
398 
399 
409  public static function whenWeekDayName($wday)
410  {
411  switch ((int) $wday) {
412  case 0 : return translate('sunday');
413  case 1 : return translate('monday');
414  case 2 : return translate('tuesday');
415  case 3 : return translate('wednesday');
416  case 4 : return translate('thursday');
417  case 5 : return translate('friday');
418  case 6 : return translate('saturday');
419  default :
420  trigger_localised_error('CRON0034', E_USER_NOTICE, $wday);
421  return '';
422  }//end switch
423 
424  }//end whenWeekDayName()
425 
426 
435  public function readableWhen($inc_type=TRUE)
436  {
437  $when_arr = Cron_Job::getWhenArr($this->attr('when'));
438  $ret_val = ($inc_type) ? Cron_Job::whenTypeName($when_arr['type']).' '.strtolower(translate('at')).' ' : '';
439  if ($when_arr['type'] != 'TI') {
440  // do not do padding for repeating time interval cron job (TI)
441  if (intval($when_arr['minute']) < 10 && strlen($when_arr['minute']) == 1) {
442  $when_arr['minute'] = '0'.$when_arr['minute'];
443  }
444  }
445  $when_arr['hour'] = str_replace(':', '', $when_arr['hour']);
446  switch ($when_arr['type']) {
447  case 'OO' :
448  $ret_val .= translate('cron_run_other', $when_arr['hour'].':'.$when_arr['minute'], ltrim($when_arr['day'], '0').' '.date('M', mktime(0, 0, 0, (int) $when_arr['month'], 1, 2000)).', '.$when_arr['year']);
449  break;
450 
451  case 'YR' :
452  $ret_val .= translate('cron_run_other', $when_arr['hour'].':'.$when_arr['minute'], $when_arr['day'].' '.date('M', mktime(0, 0, 0, (int) $when_arr['month'], 1, 2000)));
453  break;
454 
455  case 'MT' :
456  $ret_val .= translate('cron_run_month' ,$when_arr['hour'].':'.$when_arr['minute'], date('jS', mktime(0, 0, 0, 1, (int) $when_arr['day'], 2000)));
457  break;
458 
459  case 'WK' :
460  $ret_val .= translate('cron_run_other', $when_arr['hour'].':'.$when_arr['minute'], Cron_Job::whenWeekDayName($when_arr['day']).'\'s');
461  break;
462 
463  case 'DL' :
464  $ret_val .= $when_arr['hour'].':'.$when_arr['minute'];
465  break;
466 
467  case 'HR' :
468  $ret_val .= translate('cron_run_hourly', $when_arr['minute']);
469  break;
470 
471  case 'ET' :
472  $ret_val = translate('cron_run_always');
473  break;
474 
475  case 'TI' :
476  if ($when_arr['minute'] != '') {
477  $interval_unit = 'minute';
478  } else if ($when_arr['hour'] != '') {
479  $interval_unit = 'hour';
480  } else if ($when_arr['day'] != '') {
481  $interval_unit = 'day';
482  }
483  // format date, eg. 02:00 on 21 Jan, 2006
484  $next_run = date('H:i \o\n j M, Y',$when_arr['duration']);
485  $ret_val = translate('cron_run_repeat',$next_run, $when_arr[$interval_unit], translate($interval_unit.'s'));
486  break;
487 
488  }//end switch
489 
490  return $ret_val;
491 
492  }//end readableWhen()
493 
494 
504  public static function getWhenArr($when_str)
505  {
506  $when_arr = Array('type' => '', 'year' => '', 'month' => '', 'day' => '', 'hour' => '', 'minute' => '', 'duration' => '', 'period' => '');
507 
508  if ($when_str) {
509  if (strpos($when_str, '!')) {
510  $when_arr['type'] = substr($when_str, 0, 2);
511  $when_arr['duration'] = substr($when_str, strpos($when_str, '!') + 2);
512  $when_arr['period'] = substr($when_str, strpos($when_str, '!') + 1, 1);
513  } else {
514  $when_arr['type'] = substr($when_str, 0, 2);
515  $offset = 3;
516  switch ($when_arr['type']) {
517  case 'TI' :
518  $time_unit = Array(
519  60 => 'minute',
520  3600 => 'hour',
521  86400 => 'day',
522  );
523 
524  $when = substr($when_str, $offset);
525  $val = explode(',', $when);
526  $next_run = $val[0];
527  $interval = $val[1];
528  $interval_unit = (int)$val[2];
529  // store the next run time and interval info of this repeating job
530  $when_arr['duration'] = $next_run;
531  $when_arr[$time_unit[$interval_unit]] = $interval;
532  break;
533  case 'OO' :
534  $when_arr['year'] = substr($when_str, $offset, 4);
535  $offset += 5;
536  // deliberately fall through
537  case 'YR' :
538  $when_arr['month'] = substr($when_str, $offset, 2);
539  $offset += 3;
540  // deliberately fall through
541  case 'MT' :
542  case 'WK' :
543  // because the weekly type uses only one char, we do this
544  if ($when_arr['type'] == 'WK') {
545  $when_arr['day'] = substr($when_str, $offset, 1);
546  $offset += 2;
547  // else the monthly, yearly and one off use 2 chars
548  } else {
549  $when_arr['day'] = substr($when_str, $offset, 2);
550  $offset += 3;
551  }
552  // deliberately fall through
553  case 'DL' :
554  $when_arr['hour'] = substr($when_str, $offset, 2);
555  $offset += 3;
556  // deliberately fall through
557  case 'HR' :
558  $when_arr['minute'] = substr($when_str, $offset, 2);
559  $offset += 3;
560  // deliberately fall through
561  case 'PE' :
562  $when_arr['period'] = substr($when_str, $offset, 1);
563  $offset += 1;
564  // deliberately fall through
565  case 'DU' :
566  $when_arr['duration'] = substr($when_str, $offset);
567  break;
568 
569  }//end switch
570 
571  }//end else
572 
573  }//end if
574 
575  return $when_arr;
576 
577  }//end getWhenArr()
578 
579 
589  public function run()
590  {
591  $this->_tmp['running_errors'] = '';
592  set_error_handler(Array(&$this, '_errorHandler'));
593 
594  $exec_result = 0;
595  $exec_msg = '';
596 
597  // First let's see if we can find someone to run as
598  $user = $this->getRunningUser();
599  if (!is_null($user)) {
600 
601  // Now let's get the total lock
602  if ($GLOBALS['SQ_SYSTEM']->am->acquireLock($this->id, 'all')) {
603 
604  if ($GLOBALS['SQ_SYSTEM']->setCurrentUser($user)) {
605 
606  // check if we should run the repeating cron job (TI) this time
607  $when_str = $this->attr('when');
608  $start_time_ok = time() > $this->getNextRun($when_str);
609  if (($this->attr('type') == 'repeating' && substr($when_str, 0, 2) == 'TI') && !$start_time_ok) {
610  // not running yet, skip this run
611  $exec_result = SQ_CRON_JOB_NOT_COMPLETE;
612  } else {
613  $exec_result = $this->_exec($exec_msg);
614  }
615 
616  // reset the system user to who it was originally
617  $GLOBALS['SQ_SYSTEM']->restoreCurrentUser();
618 
619  // else couldn't set the current user
620  } else {
621  trigger_localised_error('CRON0027', E_USER_WARNING, $this->name, $this->id, $user->name, $user->id);
622  // we can retry because the user may have just been being edited, or something
623  $exec_result = SQ_CRON_JOB_ERROR | SQ_CRON_JOB_RETRY;
624  }
625 
626  $GLOBALS['SQ_SYSTEM']->am->releaseLock($this->id, 'all');
627 
628  // else acquireLock failed
629  } else {
630  trigger_localised_error('CRON0025', E_USER_WARNING, $this->name, $this->id);
631  // we can retry because the lock might be free by the time that we try again
632  $exec_result = SQ_CRON_JOB_ERROR | SQ_CRON_JOB_RETRY;
633 
634  }//end if
635 
636  // else no running user
637  } else {
638  trigger_localised_error('CRON0026', E_USER_WARNING, $this->name, $this->id);
639  // we can be removed because without the running user we can't do anything
640  $exec_result = SQ_CRON_JOB_ERROR | SQ_CRON_JOB_REMOVE;
641 
642  }//end if
643 
644  restore_error_handler();
645 
646  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
647  $ms->openQueue();
648  $msg = $ms->newMessage();
649 
650  $msg_reps = Array(
651  'cron_type_code' => $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name'),
652  'cron_job_name' => $this->name,
653  'cron_job_when' => $this->readableWhen(),
654  );
655 
656  $msg->from = 0; // a system message
657 
658  // if no user was found resort to sys admins
659  if (is_null($user)) {
660  $root_user = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('root_user');
661  $sys_admins = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('system_user_group');
662  $msg->to = Array($root_user->id, $sys_admins->id);
663  } else {
664  $msg->to = Array($user->id);
665  }
666 
667  // Exec failed
668  if ($exec_result & SQ_CRON_JOB_ERROR) {
669  $msg->type = 'cron.job.fail';
670 
671  // Exec successful and completed
672  } else if ($exec_result & SQ_CRON_JOB_COMPLETED) {
673  $msg->type = 'cron.job.success';
674 
675  // If we are a one off type and we aren't yet being removed do so
676  if ($this->attr('type') == 'one_off' && !($exec_result & SQ_CRON_JOB_REMOVE)) {
677  if ($exec_result & SQ_CRON_JOB_RETRY) {
678  // delete any retry attempts
679  $exec_result &= ~SQ_CRON_JOB_RETRY;
680  }
681  $exec_result |= SQ_CRON_JOB_REMOVE;
682  }
683 
684  // If this is a repeating cron job with a time interval, update the next run time
685  if ($this->attr('type') == 'repeating') {
686  $when_str = $this->attr('when');
687  $type = substr($when_str, 0, 2);
688  if ($type == 'TI') $this->setNextRun();
689  }
690 
691  // Exec successful and but still more to do
692  } else if ($exec_result & SQ_CRON_JOB_NOT_COMPLETE) {
693  $msg->type = 'cron.job.success.incomplete';
694 
695  // Exec stuffed up, email the sys admins - SHOULD NEVER HAPPEN ;)
696  } else {
697  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
698  $msg_reps['result'] = $exec_result;
699  $msg_reps['result_bits'] = implode(', ', get_bit_names('SQ_CRON_JOB_', $exec_result, TRUE));
700  $msg->type = 'cron.job.error';
701 
702  $root_user = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('root_user');
703  $sys_admins = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('system_user_group');
704  $msg->to[] = 0; // default and tech email
705  $msg->to[] = $root_user->id;
706  $msg->to[] = $sys_admins->id;
707 
708  }//end if
709 
710  $msg_reps['exec_msg'] = $exec_msg;
711 
712  if (!empty($this->_tmp['running_errors'])) {
713  $msg_reps['errors'] = $this->_tmp['running_errors'];
714  } else {
715  $msg_reps['errors'] = 'Nil';
716  }
717 
718  $msg->replacements = $msg_reps;
719  $ms->enqueueMessage($msg);
720  $ms->closeQueue();
721 
722  return $exec_result;
723 
724  }//end run()
725 
726 
739  public function _errorHandler($err_no, $err_msg, $err_file, $err_line)
740  {
741  $terminate = ((E_USER_ERROR | E_ERROR) & $err_no);
742 
743  // if the function didn't have an '@' prepended OR if we are about to terminate
744  // catch the error
745  if ((error_reporting() & $err_no) || $terminate) {
746 
747  // Strip out the file path begining
748  $err_file = hide_system_root($err_file);
749  $err_msg = hide_system_root($err_msg);
750 
751  $err_name = get_error_name($err_no);
752 
753  $err_msg = strip_tags(preg_replace(Array('/<br\\/?>/i', '/<p[^>]*>/i'), Array("\n", "\n\n"), $err_msg));
754 
755  // send a report to the system error log
756  if (ini_get('log_errors')) {
757  $cron_mgr = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('cron_manager');
758  log_error($err_msg, $err_no, $err_file, $err_line, $cron_mgr->error_log_file_name);
759  }
760 
761  // if they haven't put tags in the err msg assume it to be plain text
762  $lines = explode("\n", $err_msg);
763  $len = 7 + strlen($err_file);
764  $len = max($len, 7 + strlen($err_line));
765  foreach ($lines as $line) {
766  $len = max($len, strlen($line));
767  }
768  $len += 2;
769  $str = '+'.str_repeat('-', $len)."+\n".
770  '| '.$err_name.str_repeat(' ', $len - 2 - strlen($err_name))." |\n".
771  '|'.str_repeat('-', $len)."|\n".
772  '| File : '.$err_file.str_repeat(' ', $len - 9 - strlen($err_file))." |\n".
773  '| Line : '.$err_line.str_repeat(' ', $len - 9 - strlen($err_line))." |\n".
774  '|'.str_repeat('-', $len)."|\n";
775  foreach ($lines as $line) {
776  $str .= '| '.$line.str_repeat(' ', $len - 2 - strlen($line))." |\n";
777  }
778 
779  $str .= '+'.str_repeat('-', $len)."+\n";
780 
781  if (!isset($this->_tmp['running_errors'])) {
782  $this->_tmp['running_errors'] = '';
783  }
784  $this->_tmp['running_errors'] .= $str;
785 
786  }//end if error_reporting
787 
788  // if this is an irrercoverable error, send a distress email, delete ourselves and die
789  if ($terminate && empty($this->_tmp['terminate_error'])) {
790  $this->_tmp['terminate_error'] = TRUE; // stop possible recursion
791 
792  $cm = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('cron_manager');
793  $cm->removeJob($this);
794 
795  $user = $this->getRunningUser();
796  $root_user = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('root_user');
797  $sys_admins = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('system_user_group');
798 
799  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
800  $msg = $ms->newMessage();
801 
802  $msg_reps = Array(
803  'cron_type_code' => $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name'),
804  'asset_name' => $this->name,
805  'assetid' => $this->id,
806  'errors' => $this->_tmp['running_errors'],
807  );
808 
809  // userid zero sends a msg to SQ_CONF_DEFAULT_EMAIL and SQ_CONF_TECH_EMAIL
810  $msg->to = Array(0, $user->id, $root_user->id, $sys_admins->id);
811  $msg->from = 0; // a system message
812  $msg->type = 'cron.job.term';
813  $msg->replacements = $msg_reps;
814 
815  $msg->send();
816 
817  exit(1);
818 
819  }//end if
820 
821  }//end _errorHandler()
822 
823 
838  protected function _exec(&$msg)
839  {
840  trigger_localised_error('CRON0028', E_USER_WARNING, $this->name, $this->id);
841  // we can be removed because we haven't been done
842  return SQ_CRON_JOB_ERROR | SQ_CRON_JOB_REMOVE;
843 
844  }//end _exec()
845 
846 
857  public function getNextRun($when_str, $start_ts=NULL)
858  {
859  $when = Cron_Job::getWhenArr($when_str);
860 
861  // One Off is simple
862  if ($when['type'] == 'OO') {
863  return mktime($when['hour'], $when['minute'], 0, $when['month'], $when['day'], $when['year']);
864  }
865 
866  if (is_null($start_ts)) $start_ts = time();
867  $start = getdate($start_ts);
868 
869  // EveryTime is even simpler
870  if ($when['type'] == 'ET') return $start_ts + 1;
871 
872  // Repeating Job with time interval
873  if ($when['type'] == 'TI') return $when['duration'];
874 
875  $add = Array('minutes' => 0, 'hours' => 0, 'days' => 0);
876 
877  $hour_loops = ($when['minute'] < $start['minutes']);
878  $day_loops = ($when['hour'] < $start['hours'] || ($when['hour'] == $start['hours'] && $hour_loops));
879  $week_loops = ($when['day'] < $start['wday'] || ($when['day'] == $start['wday'] && $day_loops));
880  $month_loops = ($when['day'] < $start['mday'] || ($when['day'] == $start['mday'] && $day_loops));
881  $year_loops = ($when['month'] < $start['mon'] || ($when['month'] == $start['mon'] && $month_loops));
882 
883  switch ($when['type']) {
884  // Hourly
885  case 'HR' :
886  // if we need to loop over until the next hour
887  if ($hour_loops) {
888  $add['minutes'] += (60 - $start['minutes']) + $when['minute'];
889  } else {
890  $add['minutes'] += $when['minute'] - $start['minutes'];
891  }
892 
893  break;
894 
895  // Daily
896  case 'DL' :
897  // if we need to loop over until the next day
898  if ($day_loops) {
899  $add['hours'] += (24 - $start['hours']) + $when['hour'];
900  } else {
901  $add['hours'] += $when['hour'] - $start['hours'];
902  }
903 
904  $add['minutes'] += $when['minute'] - $start['minutes'];
905 
906  break;
907 
908  // Weekly
909  case 'WK' :
910  // if we need to loop over until the next week
911  if ($week_loops) {
912  $add['days'] += (7 - $start['wday']) + $when['day'];
913  } else {
914  $add['days'] += $when['day'] - $start['wday'];
915  }
916 
917  $add['hours'] += $when['hour'] - $start['hours'];
918  $add['minutes'] += $when['minute'] - $start['minutes'];
919 
920  break;
921 
922  // Monthly
923  case 'MT' :
924  // if we need to loop over until the next month
925  if ($month_loops) {
926  require_once SQ_FUDGE_PATH.'/general/datetime.inc';
927  $add['days'] += (days_in_month($start['mon'], $start['year']) - $start['mday']) + $when['day'];
928  } else {
929  $add['days'] += $when['day'] - $start['mday'];
930  }
931 
932  $add['hours'] += $when['hour'] - $start['hours'];
933  $add['minutes'] += $when['minute'] - $start['minutes'];
934 
935  break;
936 
937  // Yearly
938  case 'YR' :
939 
940  require_once SQ_FUDGE_PATH.'/general/datetime.inc';
941 
942  // if we need to loop over until the next year
943  if ($year_loops) {
944 
945  // add the rest of the months this year
946  for ($i = $start['mon']; $i <= 12; $i++) {
947  $add['days'] += days_in_month($i, $start['year']);
948  }
949  // add the months for next year
950  for ($i = 1; $i < $when['month']; $i++) {
951  $add['days'] += days_in_month($i, $start['year'] + 1);
952  }
953 
954  } else {
955 
956  // add the months between
957  for ($i = $start['mon']; $i < $when['month']; $i++) {
958  $add['days'] += days_in_month($i, $start['year']);
959  }
960 
961  }//end if
962 
963  $add['days'] += $when['day'] - $start['mday'];
964  $add['hours'] += $when['hour'] - $start['hours'];
965  $add['minutes'] += $when['minute'] - $start['minutes'];
966 
967 
968  break;
969 
970  }//end switch
971 
972  $add['hours'] += $add['days'] * 24;
973  $add['minutes'] += $add['hours'] * 60;
974 
975  return $start_ts + ($add['minutes'] * 60);
976 
977  }//end getNextRun()
978 
979 
988  public function setNextRun()
989  {
990  if ($this->attr('type') == 'repeating') {
991  $when_str = $this->attr('when');
992  $type = substr($when_str, 0, 2);
993  if ($type == 'TI') {
994  $when = substr($when_str, 3);
995  $val = explode(',', $when);
996  $old_run = $val[0];
997  $interval = $val[1];
998  $interval_unit = $val[2];
999  $next_run = $old_run + ($interval * $interval_unit);
1000 
1001  while ($next_run < time()) {
1002  // if this is a long running job, and we cannot start because it is still running
1003  // then we have to skip a few turns to make sure next run time is in the future
1004 
1005  $next_run = $old_run + ($interval * $interval_unit);
1006  $old_run = $next_run;
1007 
1008 
1009  if ($next_run < time()) {
1010  // send a system email to inform admins about this,
1011  // so that they can increase the time interval between each run
1012  $root_user = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('root_user');
1013  $sys_admins = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('system_user_group');
1014 
1015  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
1016  $ms->openQueue();
1017  $msg = $ms->newMessage();
1018  $msg_reps = Array(
1019  'cron_type_code' => $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name'),
1020  'asset_name' => $this->name,
1021  'assetid' => $this->id,
1022  'run_time' => easy_datetime($next_run),
1023  );
1024  $msg->to = Array($root_user->id, $sys_admins->id);
1025  $msg->from = 0; // a system message
1026  $msg->type = 'cron.job.startfail';
1027  $msg->replacements = $msg_reps;
1028 
1029  $ms->enqueueMessage($msg);
1030  $ms->closeQueue();
1031  }
1032  }//end while
1033 
1034  $GLOBALS['SQ_SYSTEM']->setRunLevel(SQ_RUN_LEVEL_FORCED + SQ_SECURITY_LINK_INTEGRITY);
1035  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
1036  if ($GLOBALS['SQ_SYSTEM']->am->acquireLock($this->id, 'links')) {
1037  $this->setAttrValue('when', "TI=$next_run,$interval,$interval_unit");
1038  $this->saveAttributes();
1039  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
1040  $GLOBALS['SQ_SYSTEM']->am->releaseLock($this->id, 'links');
1041  }
1042  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
1043  }//end if
1044  }//end if
1045 
1046  }//end setNextRun()
1047 
1048 
1056  public function removeJob()
1057  {
1058  $am = $GLOBALS['SQ_SYSTEM']->am;
1059  // if we are type2 linked under the manager, don't worry.
1060  // if we are only type3 or notice linked under the manager, trash ourselves
1061  $cron_manager_links = $am->getLinks($this->id, SQ_LINK_TYPE_2, 'cron_manager', TRUE, 'minor');
1062  if (empty($cron_manager_links)) {
1063  // clean up any notice links in which we are the major
1064  $notice_links = $am->getLinks($this->id, SQ_LINK_NOTICE, NULL, NULL, 'major');
1065 
1066  foreach ($notice_links as $notice_link) {
1067  $am->deleteAssetLink($notice_link['linkid']);
1068  }
1069 
1070  $trash = $am->getAsset($am->getSystemAssetid('trash_folder'));
1071  $trash->createLink($this, SQ_LINK_TYPE_1);
1072  }
1073 
1074  }//end removeJob()
1075 
1076 
1077 }//end class
1078 ?>