Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
cron_manager.inc
1 <?php
17 require_once SQ_INCLUDE_PATH.'/asset.inc';
18 require_once SQ_CORE_PACKAGE_PATH.'/system/cron/cron_job/cron_job.inc';
19 
31 class Cron_Manager extends Asset
32 {
33 
37  var $error_log_file_name = 'cron_errors';
38 
39 
46  function __construct($assetid=0)
47  {
48  $this->_ser_attrs = TRUE;
49  parent::__construct($assetid);
50 
51  // including these assets here as they are required to send an error
52  // message in the case that a cron job run() fails. necessary because
53  // getSystemAsset() will fail because it cannot includeAsset() inside
54  // an aborted transaction
55  $GLOBALS['SQ_SYSTEM']->am->includeAsset('root_user');
56  $GLOBALS['SQ_SYSTEM']->am->includeAsset('system_user_group');
57 
58  }//end constructor
59 
60 
72  public function create(Array &$link)
73  {
74  require_once SQ_CORE_PACKAGE_PATH.'/system/system_asset_fns.inc';
75  if (!system_asset_fns_create_pre_check($this)) {
76  return FALSE;
77  }
78  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
79  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
80 
81  if ($linkid = parent::create($link)) {
82  if (!system_asset_fns_create_cleanup($this)) {
83  $linkid = FALSE;
84  }
85  }
86 
87  if ($linkid) {
88  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
89  } else {
90  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
91  }
92 
93  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
94  return $linkid;
95 
96  }//end create()
97 
98 
108  protected function _getName($short_name=FALSE)
109  {
110  return $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name');
111 
112  }//end _getName()
113 
114 
122  public function _getAllowedLinks()
123  {
124  return Array(
125  SQ_LINK_TYPE_1 => Array('cron_job' => Array('card' => 'M', 'exclusive' => TRUE)),
126  SQ_LINK_TYPE_2 => Array('cron_job' => Array('card' => 'M', 'exclusive' => TRUE)),
127  SQ_LINK_TYPE_3 => Array('cron_job' => Array('card' => 'M', 'exclusive' => TRUE)),
128  SQ_LINK_NOTICE => Array(),
129  );
130 
131  }//end _getAllowedLinks()
132 
133 
140  public function lockTypes()
141  {
142  $lock_types = parent::lockTypes();
143  $lock_types['attr_links'] = ($lock_types['attributes'] | $lock_types['links']);
144  return $lock_types;
145 
146  }//end lockTypes()
147 
148 
155  public function canClone()
156  {
157  return FALSE;
158 
159  }//end canClone()
160 
161 
172  public function morph($new_type_code)
173  {
174  trigger_localised_error('CRON0024', E_USER_WARNING, $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name'));
175  return FALSE;
176 
177  }//end morph()
178 
179 
191  public function moveLinkPos($linkid, $sort_order=-1)
192  {
193  trigger_localised_error('CRON0041', E_USER_WARNING, $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name'));
194  return FALSE;
195 
196  }//end moveLinkPos()
197 
198 
209  public function deleteLink($linkid, $check_locked=TRUE)
210  {
211  $link = $GLOBALS['SQ_SYSTEM']->am->getLinkById($linkid, $this->id);
212  if (empty($link)) {
213  trigger_localised_error('SYS0243', E_USER_NOTICE, $linkid);
214  return FALSE;
215  }
216 
217  if ($GLOBALS['SQ_SYSTEM']->am->isSystemAssetType($link['minor_type_code'])){
218  trigger_localised_error('CORE0118', E_USER_WARNING, $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name'));
219  return FALSE;
220  }
221 
222  return parent::deleteLink($linkid, $check_locked);
223 
224  }//end deleteLink()
225 
226 
236  public function setAttrValue($name, $value)
237  {
238  if (!$GLOBALS['SQ_SYSTEM']->userRoot()) {
239  trigger_localised_error('CRON0025', E_USER_WARNING, $name, $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name'));
240  return FALSE;
241  }
242 
243  // the refresh time has some restrictions on it, because otherwise painting the when box would be a pain
244  if ($name == 'refresh_time') {
245  $value = (int) $value;
246 
247  $one_hour = 60 * 60;
248  $one_day = 24 * $one_hour;
249 
250  if ($value >= $one_day) {
251  // if we are over a day we must be a multiple of one day
252  if ($value % $one_day) {
253  trigger_localised_error('CRON0035', E_USER_WARNING, $one_day);
254  return FALSE;
255  }
256  } else if ($value >= $one_hour) {
257  // if we are over an hour we must be a multiple of one hour
258  if ($value % $one_hour || $one_day % $value) {
259  trigger_localised_error('CRON0036', E_USER_WARNING, $one_hour);
260  return FALSE;
261  }
262  } else {
263  // if we less than an hour we must divide evenly into an hour
264  if ($one_hour % $value) {
265  trigger_localised_error('CRON0037', E_USER_WARNING, $one_hour);
266  return FALSE;
267  }
268  }
269  }//end if setting refresh_time
270 
271  // if we are setting the last run time and the epoc has not been set yet,
272  // assume the last run was the first run and set the epoc
273  if ($name == 'last_run' && $this->attr('epoch') == 0) {
274  $this->setAttrValue('epoch', $value);
275  }
276 
277  return parent::setAttrValue($name, $value);
278 
279  }//end setAttrValue()
280 
281 
293  public function addJob(Cron_Job $job, User $user, $link_type=SQ_LINK_TYPE_3, $read_only=FALSE)
294  {
295  $job->setAttrValue('running_as', $user->id);
296  $job->setAttrValue('read_only', $read_only);
297 
298  $link = Array('asset' => $this, 'link_type' => $link_type, 'is_exclusive' => '1');
299 
300  $GLOBALS['SQ_SYSTEM']->setRunLevel(SQ_RUN_LEVEL_FORCED);
301  $linkid = $job->create($link);
302  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
303 
304  return ($linkid > 0);
305 
306  }//end addJob()
307 
308 
317  public function removeJob(Cron_Job $job)
318  {
319  $link = $GLOBALS['SQ_SYSTEM']->am->getLinkByAsset($this->id, $job->id, SQ_LINK_TYPE_3);
320  if (empty($link)) return FALSE;
321 
322  $GLOBALS['SQ_SYSTEM']->setRunLevel(SQ_RUN_LEVEL_FORCED);
323  $job->removeJob();
324  $success = $this->deleteLink($link['linkid']);
325  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
326 
327  return $success;
328 
329  }//end removeJob()
330 
331 
346  public function getJobs($type_code='cron_job', $strict_type_code=TRUE)
347  {
348  $links = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_SC_LINK_SIGNIFICANT, $type_code, $strict_type_code, 'major', NULL, NULL, TRUE);
349  $jobs = Array();
350  foreach ($links as $link) {
351  $job = $GLOBALS['SQ_SYSTEM']->am->getAsset($link['minorid'], $link['minor_type_code']);
352  if (is_null($job)) continue;
353  $jobs[] = $job;
354  }
355 
356  return $jobs;
357 
358  }//end getJobs()
359 
360 
367  public function timeOfNextRun()
368  {
369  return max((int) $this->attr('last_run'), (int) $this->attr('epoch')) + (int) $this->attr('refresh_time');
370 
371  }//end timeOfNextRun()
372 
373 
380  public function readableRefreshTime()
381  {
382  $refresh_time = (int) $this->attr('refresh_time');
383  if ($refresh_time >= 86400) {
384  $num = ($refresh_time / 86400);
385  return ($num > 1) ? $num.' '.translate('days') : translate('day');
386  }
387  if ($refresh_time >= 3600) {
388  $num = ($refresh_time / 3600);
389  return ($num > 1) ? $num.' '.translate('hours') : translate('hour');
390  }
391  if ($refresh_time >= 60) {
392  $num = ($refresh_time / 60);
393  return ($num > 1) ? $num.' '.translate('minutes') : translate('minute');
394  }
395  return $refresh_time.' '.translate('seconds');
396 
397  }//end readableRefreshTime()
398 
399 
408  public function isExcludedTime($start_time)
409  {
410 
411  $days_array = Array(
412  0 => 'Sun',
413  1 => 'Mon',
414  2 => 'Tue',
415  3 => 'Wed',
416  4 => 'Thu',
417  5 => 'Fri',
418  6 => 'Sat',
419  );
420  $excluded_times = $this->attr('exclude_times');
421 
422  foreach ($excluded_times as $exclude_time) {
423  $excluded_days = Array();
424  foreach ($exclude_time['days'] as $excluded_day) {
425  $excluded_days[] = $days_array[$excluded_day];
426  }
427 
428  $now_day = date('D', $start_time); // A textual representation of a day, three letters, eg. 'Mon'
429  $now_time = date('G', $start_time); // 24-hour format of an hour without leading zeros, eg. 8, 15
430  if (in_array($now_day, $excluded_days)) {
431  // range: [from, to)
432  if ($now_time >= $exclude_time['from'] && (($exclude_time['to'] < $exclude_time['from']) || ($now_time < $exclude_time['to']))) {
433  return TRUE;
434  }
435  } else if ($exclude_time['from'] > $exclude_time['to']) {
436  // exclude from previous day X hour to today Y hour
437  $yesterday_day = date('D', time()-86400);
438  if (in_array($yesterday_day, $excluded_days)) {
439  if ($now_time < $exclude_time['to']) return TRUE;
440  }
441  }
442  }
443  return FALSE;
444 
445  }//end isExcludedTime()
446 
447 
454  public function run()
455  {
456  $start_time = time();
457 
458  $old_log_errors = ini_set('log_errors', '1');
459  $old_error_log = ini_set('error_log', SQ_LOG_PATH.'/'.$this->error_log_file_name.SQ_CONF_LOG_EXTENSION);
460  set_error_handler(Array($this, '_errorHandler'));
461 
462  if ($this->isExcludedTime($start_time)) {
463  trigger_localised_error('CRON0047', E_USER_NOTICE, $this->name);
464  return;
465  }
466 
467  if (!empty($GLOBALS['SQ_SYSTEM']->tm) && $GLOBALS['SQ_SYSTEM']->tm->inTransaction(MatrixDAL::getCurrentDbId())) {
468  trigger_localised_error('CRON0012', E_USER_WARNING);
469  return;
470  }
471 
472  if ($start_time < (int) $this->attr('epoch')) {
473  trigger_localised_error('CRON0011', E_USER_WARNING);
474  return;
475  }
476 
477  // holds the assetids for potential long jobs
478  // running flag for cron manager is set to false while running these jobs
479  // we use the running flag in the cron job to control the logic
480  $long_jobs = Array();
481 
482  // if we are currently running then we should just leave this alone
483  if ($this->attr('running')) {
484  // if we have been stopped too many times without the while loop below
485  // looping over
486  if ((int) $this->attr('run_check') >= (int) $this->attr('warn_after_num_run_checks')) {
487 
488  $root_user = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('root_user');
489  $sys_admins = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('system_user_group');
490 
491  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
492  $msg = $ms->newMessage();
493  $msg_reps = Array(
494  'system_name' => SQ_CONF_SYSTEM_NAME,
495  'num_attempts' => $this->attr('warn_after_num_run_checks'),
496  'error_log_path' => SQ_LOG_PATH.'/'.$this->error_log_file_name.SQ_CONF_LOG_EXTENSION,
497  'root_urls' => str_replace("\n", ',', SQ_CONF_SYSTEM_ROOT_URLS),
498  );
499 
500  // SQ_CONF_DEFAULT_EMAIL, SQ_CONF_TECH_EMAIL, and root user as default we send the email to
501  // get the other users configured to send the email to
502  $config_user = $this->attr('dead_lock_email_to');
503 
504  // userid zero sends a msg to SQ_CONF_DEFAULT_EMAIL and SQ_CONF_TECH_EMAIL
505  $msg->to = array_merge(Array(0, $root_user->id), $config_user);
506  $msg->from = 0; // a system message
507  $msg->type = 'cron.deadlock';
508  $msg->replacements = $msg_reps;
509 
510  $msg->send();
511 
512  }//end if run_check
513 
514 
515  // now update this last run time, so that we know for next time
516  if ($GLOBALS['SQ_SYSTEM']->am->acquireLock($this->id, 'attributes', 0, TRUE)) {
517  $this->setAttrValue('run_check', (int) $this->attr('run_check') + 1);
518  $this->saveAttributes();
519  $GLOBALS['SQ_SYSTEM']->am->releaseLock($this->id, 'attributes');
520 
521  } else {
522  trigger_localised_error('CRON0016', E_USER_WARNING, $this->name);
523  }
524 
525  } else {
526  // we aren't already running
527  $this->setAttrValue('current_job', NULL);
528 
529  // OK using the epoch time and the the refresh time figure when the this period "really" started
530  $epoch = (int) $this->attr('epoch');
531  $refresh_time = (int) $this->attr('refresh_time');
532 
533  $secs = $start_time - $epoch;
534  $periods = floor($secs / $refresh_time);
535  $this_run = $epoch + ($refresh_time * $periods);
536 
537  // if we only every day (or higher) we need to take into account the effects of
538  // daylight savings
539  if ($refresh_time >= (60 * 60 * 24)) {
540  $epoch_in_dst = (date('I', $epoch) == '1');
541  $start_in_dst = (date('I', $start_time) == '1');
542 
543  // if the epoch and start time are in different stages of daylight savings we need to adjust
544  if ($epoch_in_dst != $start_in_dst) {
545  // because the epoch is before the start time, if it is in daylight savings
546  // then we need to add one hour to this run's time
547  // otherwise we need to drop an hour
548  $this_run += ($epoch_in_dst) ? +3600 : -3600;
549  }
550  }
551 
552  // now get the lock, set the running flag and reset the run_check
553  if ($GLOBALS['SQ_SYSTEM']->am->acquireLock($this->id, 'attributes', 0, TRUE) && $this->setAttrValue('running', TRUE) && $this->setAttrValue('run_check', 0) && $this->saveAttributes()) {
554 
555  $GLOBALS['SQ_SYSTEM']->am->releaseLock($this->id, 'attributes');
556 
557  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
558  $am = $GLOBALS['SQ_SYSTEM']->am;
559 
560  // first get some times
561  $last_run = (int) $this->attr('last_run');
562  $next_run = $this_run + $refresh_time;
563 
564  $jobs = $this->getJobsToRun($last_run, $this_run);
565 
566  // holds the number of times that a job has failed
567  // in the form: Array([assetid] => [number of failed runs]);
568  $failed_runs = Array();
569 
570  // holds the assetids for jobs to ignore - because they have failed to many times
571  // in the form: Array([assetid]);
572  $ignore_jobs = Array();
573 
574  while (!empty($jobs)) {
575 
576  $ms->openQueue();
577  foreach ($jobs as $job_info) {
578 
579  $cron_job = $am->getAsset($job_info['assetid'], $job_info['type_code']);
580  if (is_null($cron_job)) continue;
581 
582  if ($cron_job->attr('long')) {
583  // ignore potential long jobs, and run them later
584  // with cron manager running flag as false
585  // with cron job running flag as true
586  if (!$cron_job->attr('running')) {
587  // only include the long job if it is not already running
588  $long_jobs[] = $job_info;
589  }
590  $ignore_jobs[] = $cron_job->id;
591  $am->forgetAsset($cron_job);
592  continue;
593 
594  } else {
595 
596  $GLOBALS['SQ_SYSTEM']->setRunLevel(SQ_RUN_LEVEL_FORCED);
597  $this->setAttrValue('current_job', $cron_job->id);
598  $this->saveAttributes();
599  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
600 
601  $result = $cron_job->run();
602 
603  $GLOBALS['SQ_SYSTEM']->setRunLevel(SQ_RUN_LEVEL_FORCED);
604  $this->setAttrValue('current_job', NULL);
605  $this->saveAttributes();
606  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
607  }
608 
609  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
610 
611  // we have been asked to remove this cron job
612  if ($result & SQ_CRON_JOB_REMOVE) {
613  $this->removeJob($cron_job);
614  $ignore_jobs[] = $cron_job->id;
615 
616  } else if ($result & SQ_CRON_JOB_RETRY) {
617  // they want to have another go
618 
619  // if they resulted in an error, we need to check this hasn't happened before
620  if ($result & SQ_CRON_JOB_ERROR) {
621 
622  if (isset($failed_runs[$cron_job->id])) {
623  $failed_runs[$cron_job->id]++;
624  } else {
625  $failed_runs[$cron_job->id] = 1;
626  }
627 
628  // if they have had their fair share, get rid of them
629  if ($failed_runs[$cron_job->id] >= (int) $this->attr('num_failed_attempts')) {
630  $this->removeJob($cron_job);
631  $ignore_jobs[] = $cron_job->id;
632 
633  $user = $cron_job->getRunningUser();
634  $root_user = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('root_user');
635  $sys_admins = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('system_user_group');
636 
637  $msg = $ms->newMessage();
638  $msg_reps = Array(
639  'cron_job_code' => $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($cron_job->type(), 'name'),
640  'asset_name' => $cron_job->name,
641  'assetid' => $cron_job->id,
642  );
643  $msg->to = Array($user->id, $root_user->id, $sys_admins->id);
644  $msg->from = 0; // a system message
645  $msg->type = 'cron.forced_fail';
646  $msg->replacements = $msg_reps;
647 
648  $ms->enqueueMessage($msg);
649 
650  }//end if
651 
652  }//end if error
653 
654  } else {
655  // they don't want another go but they don't want to be removed either ? just ignore them
656  $ignore_jobs[] = $cron_job->id;
657 
658  }//end if
659 
660  $am->forgetAsset($cron_job);
661 
662  }//end foreach
663 
664  // send all the messages for this run
665  $ms->closeQueue();
666 
667  // give it three seconds to get the lock
668  $lock_count = 0;
669  while (1) {
670  if ($acquired = $GLOBALS['SQ_SYSTEM']->am->acquireLock($this->id, 'attr_links', 0, TRUE)) {
671  break;
672  } else if ($lock_count < 3) {
673  $lock_count++;
674  sleep(1);
675  } else {
676  break;
677  }
678  }
679 
680  // now update this last run time, so that we know for next time
681  if ($acquired) {
682 
683  $this->setAttrValue('run_check', 0);
684  $this->saveAttributes();
685 
686  $GLOBALS['SQ_SYSTEM']->am->releaseLock($this->id, 'attr_links');
687 
688  } else {
689  trigger_localised_error('CRON0015', E_USER_WARNING, $this->name);
690  return;
691  }
692 
693  // if we have gone into the next run's time then we need to do its work as well
694  if (time() > $next_run) {
695  $this_run = $next_run;
696  $next_run += $refresh_time;
697  }
698 
699  $jobs = $this->getJobsToRun($last_run, $this_run, $ignore_jobs);
700  }//end while
701 
702  // now update this last run time, so that we know for next time
703  if ($GLOBALS['SQ_SYSTEM']->am->acquireLock($this->id, 'attributes', 0, TRUE)) {
704  if ($this->setAttrValue('last_run', $this_run) && $this->setAttrValue('running', FALSE) && $this->saveAttributes()) {
705  $GLOBALS['SQ_SYSTEM']->am->releaseLock($this->id, 'attributes');
706  } else {
707  trigger_localised_error('CRON0032', E_USER_WARNING, $this->name);
708  }
709  } else {
710  trigger_localised_error('CRON0016', E_USER_WARNING, $this->name);
711  }
712 
713  // process the potential long running jobs
714  foreach ($long_jobs as $job_info) {
715  $cron_job = $am->getAsset($job_info['assetid'], $job_info['type_code']);
716  if ($cron_job->attr('long') && !$cron_job->attr('running')) {
717  $result = $cron_job->run();
718  if ($result & SQ_CRON_JOB_REMOVE) {
719  $this->removeJob($cron_job);
720  } else if ($result & SQ_CRON_JOB_ERROR) {
721  trigger_localised_error('CRON0049', E_USER_WARNING, $cron_job->id);
722  }
723  }
724  $am->forgetAsset($cron_job);
725  }
726 
727  } else {
728  // aquire or set running failed
729  trigger_localised_error('CRON0015', E_USER_WARNING, $this->name);
730 
731  }//end else - if acquire lock
732 
733  }//end else already running
734 
735  ini_set('log_errors', $old_log_errors);
736  ini_set('error_log', $old_error_log);
737  restore_error_handler();
738 
739  }//end run()
740 
741 
752  protected function _clob2Str($expression)
753  {
754  if (MatrixDAL::getDbType() === 'oci') {
755  return 'DBMS_LOB.SUBSTR('.$expression.', DBMS_LOB.GETLENGTH('.$expression.'), 1)';
756  } else {
757  return $expression;
758  }
759 
760  }//end _clob2Str()
761 
762 
773  public function getJobsToRun($start_ts, $end_ts, $ignore_jobs=Array())
774  {
775  require_once SQ_FUDGE_PATH.'/general/datetime.inc';
776 
777  if ($end_ts < $start_ts) {
778  trigger_localised_error('CRON0014', E_USER_WARNING);
779  return Array();
780  }
781 
782  $end = getdate($end_ts);
783  $start = getdate($start_ts);
784 
785  $time_since_last_run = $end_ts - $start_ts;
786 
787  $one_hour = 60 * 60;
788  $one_day = 24 * $one_hour;
789  $one_week = 7 * $one_day;
790 
791  $over_one_hour_ago = ($time_since_last_run >= $one_hour);
792  $over_one_day_ago = ($time_since_last_run >= $one_day);
793  $over_one_week_ago = ($time_since_last_run >= $one_week);
794 
795 
796  if ($end['mon'] == 1) {
797  $days_last_month = days_in_month(12, $end['year'] - 1);
798  } else {
799  $days_last_month = days_in_month($end['mon'] - 1, $end['year']);
800  }
801  // if it is over the number of days last month, then it is over a month ago
802  $over_one_month_ago = ($time_since_last_run >= ($days_last_month * $one_day));
803 
804  $days_last_year = (is_leap_year($end['year'] - 1)) ? 366 : 365;
805  // if it is over the number of days last year, then it is over a year ago
806  $over_one_year_ago = ($time_since_last_run >= ($days_last_year * $one_day));
807 
808  // formatted some values of the date vars
809  $end_f = Array();
810  $start_f = Array();
811  foreach (Array('minutes', 'hours', 'wday', 'mday', 'mon', 'year') as $field) {
812  switch ($field) {
813  case 'wday':
814  $places = 1;
815  break;
816  case 'year':
817  $places = 4;
818  break;
819  default :
820  $places = 2;
821  break;
822  }
823  $end_f[$field] = str_pad($end[$field], $places, '0', STR_PAD_LEFT);
824  $start_f[$field] = str_pad($start[$field], $places, '0', STR_PAD_LEFT);
825  }
826 
827 
828  $build_up = Array();
829 
830  $build_up['hour']['bridge'] = '('.$this->_clob2Str('COALESCE(av.custom_val, at.default_val)').')
831  > '.MatrixDAL::quote('%A%'.$start_f['minutes']).'
832  AND ('.$this->_clob2Str('COALESCE(av.custom_val, at.default_val)').') <= '.MatrixDAL::quote('%B%59').'
833  OR ('.$this->_clob2Str('COALESCE(av.custom_val, at.default_val)').')
834  BETWEEN '.MatrixDAL::quote('%C%00').' AND '.MatrixDAL::quote('%D%'.$end_f['minutes']);
835 
836  $build_up['hour']['all'] = '('.$this->_clob2Str('COALESCE(av.custom_val, at.default_val)').') > '.MatrixDAL::quote('%A%'.$start_f['minutes']).'
837  AND ('.$this->_clob2Str('COALESCE(av.custom_val, at.default_val)').') <= '.MatrixDAL::quote('%B%'.$end_f['minutes']);
838 
839  $build_up['day']['bridge'] = str_replace(Array('%A%', '%B%', '%C%', '%D%'), Array('%A%'.$start_f['hours'].':', '%B%23:', '%C%00:', '%D%'.$end_f['hours'].':'), $build_up['hour']['bridge']);
840  $build_up['day']['all'] = str_replace(Array('%A%', '%B%'), Array('%A%'.$start_f['hours'].':', '%B%'.$end_f['hours'].':'), $build_up['hour']['all']);
841 
842  $build_up['week']['bridge'] = str_replace(Array('%A%', '%B%', '%C%', '%D%'),
843  Array('%A%'.$start_f['wday'].' ', '%B%6 ', '%C%0 ', '%D%'.$end_f['wday'].' '),
844  $build_up['day']['bridge']);
845 
846  $build_up['week']['all'] = str_replace(Array('%A%', '%B%'),
847  Array('%A%'.$start_f['wday'].' ', '%B%'.$end_f['wday'].' '),
848  $build_up['day']['all']);
849 
850  $build_up['month']['bridge'] = str_replace(Array('%A%', '%B%', '%C%', '%D%'),
851  Array('%A%'.$start_f['mday'].' ', '%B%'.days_in_month($start['mon'], $start['year']).' ', '%C%00 ', '%D%'.$end_f['mday'].' '),
852  $build_up['day']['bridge']);
853 
854  $build_up['month']['all'] = str_replace(Array('%A%', '%B%'),
855  Array('%A%'.$start_f['mday'].' ', '%B%'.$end_f['mday'].' '),
856  $build_up['day']['all']);
857 
858  $build_up['year']['bridge'] = str_replace(Array('%A%', '%B%', '%C%', '%D%'),
859  Array('%A%'.$start_f['mon'].'-', '%B%12-', '%C%00-', '%D%'.$end_f['mon'].'-'),
860  $build_up['month']['bridge']);
861 
862  $build_up['year']['all'] = str_replace(Array('%A%', '%B%'),
863  Array('%A%'.$start_f['mon'].'-', '%B%'.$end_f['mon'].'-'),
864  $build_up['month']['all']);
865 
866  $build_up['one_off']['all'] = str_replace(Array('%A%', '%B%'),
867  Array('%A%'.$start_f['year'].'-', '%B%'.$end_f['year'].'-'),
868  $build_up['year']['all']);
869 
870  $type = 'all';
871 
872  if ($over_one_hour_ago) {
873  $hourly = '('.$this->_clob2Str('COALESCE(av.custom_val, at.default_val)').') LIKE '.MatrixDAL::quote('HR=%');
874  } else {
875  // if it has ticked over the hour since the last call
876  $type = ($end['minutes'] < $start['minutes']) ? 'bridge' : 'all';
877  $hourly = preg_replace('/%[ABCD]%/', 'HR=', $build_up['hour'][$type]);
878  }
879 
880 
881  if ($over_one_day_ago) {
882  $daily = '('.$this->_clob2Str('COALESCE(av.custom_val, at.default_val)').') LIKE '.MatrixDAL::quote('DL=%');
883  } else {
884  // if it has ticked over the day since the last call
885  $type = ($end['hours'] < $start['hours']) ? 'bridge' : 'all';
886  $daily = preg_replace('/%[ABCD]%/', 'DL=', $build_up['day'][$type]);
887  }
888 
889 
890  if ($over_one_week_ago) {
891  $weekly = '('.$this->_clob2Str('COALESCE(av.custom_val, at.default_val)').') LIKE '.MatrixDAL::quote('WK=%');
892  } else {
893  // if it has ticked over the week since the last call
894  $type = ($end['wday'] < $start['wday']) ? 'bridge' : 'all';
895  $weekly = preg_replace('/%[ABCD]%/', 'WK=', $build_up['week'][$type]);
896  }
897 
898 
899  if ($over_one_month_ago) {
900  $monthly = '('.$this->_clob2Str('COALESCE(av.custom_val, at.default_val)').') LIKE '.MatrixDAL::quote('MT=%');
901  } else {
902  // if it has ticked over the month since the last call
903  $type = ($end['mday'] < $start['mday']) ? 'bridge' : 'all';
904  $monthly = preg_replace('/%[ABCD]%/', 'MT=', $build_up['month'][$type]);
905  }
906 
907 
908  if ($over_one_year_ago) {
909  $yearly = '('.$this->_clob2Str('COALESCE(av.custom_val, at.default_val)').') LIKE '.MatrixDAL::quote('YR=%');
910  } else {
911  // if it has ticked over the year since the last call
912  $type = ($end['mon'] < $start['mon']) ? 'bridge' : 'all';
913  $yearly = preg_replace('/%[ABCD]%/', 'YR=', $build_up['year'][$type]);
914  }
915 
916  $one_off = preg_replace('/%[ABCD]%/', 'OO=', $build_up['one_off']['all']);
917 
918  $every_time = '('.$this->_clob2Str('COALESCE(av.custom_val, at.default_val)').') = '.MatrixDAL::quote('ET=');
919 
920  // repeating cron job with time interval option added to the query str
921  $repeating_time_interval = 'UPPER(av.custom_val) LIKE '.MatrixDAL::quote('%TI=%');
922 
923  $sql = 'SELECT l.linkid, a.assetid, a.type_code
924  FROM ('.SQ_TABLE_RUNNING_PREFIX.'ast_lnk l
925  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast a ON l.minorid = a.assetid)
926  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_attr at ON at.type_code = a.type_code
927  LEFT OUTER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_attr_val av ON (av.assetid = a.assetid AND av.attrid = at.attrid)';
928  $where = 'l.majorid = '.MatrixDAL::quote($this->id).'
929  AND a.type_code IN (
930  SELECT type_code
931  FROM sq_ast_typ_inhd
932  WHERE inhd_type_code = '.MatrixDAL::quote('cron_job').'
933  )
934  AND at.name = '.MatrixDAL::quote('when').'
935  AND ('.$hourly.'
936  OR '.$daily.'
937  OR '.$weekly.'
938  OR '.$monthly.'
939  OR '.$yearly.'
940  OR '.$one_off.'
941  OR '.$every_time.'
942  OR '.$repeating_time_interval.'
943  )';
944 
945  if (!empty($ignore_jobs)) {
946  for ($i = 0; $i < count($ignore_jobs); $i++) {
947  $ignore_jobs[$i] = MatrixDAL::quote($ignore_jobs[$i]);
948  }
949  $where .= ' AND a.assetid NOT IN ('.implode(',', $ignore_jobs).')';
950  }
951 
952  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
953  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'av');
954 
955  try {
956  $result = MatrixDAL::executeSqlAll($sql.$where);
957  } catch (DALException $e) {
958  return Array();
959  }
960 
961  $GLOBALS['CRON_MANAGER_GET_JOBS_TO_RUN_START_TS'] = $start_ts;
962  unset($GLOBALS['CRON_MANAGER_GET_JOBS_TO_RUN_START_TS']);
963 
964  return $result;
965 
966  }//end getJobsToRun()
967 
968 
981  public function _errorHandler($err_no, $err_msg, $err_file, $err_line)
982  {
983 
984  $terminate = ((E_USER_ERROR | E_ERROR) & $err_no);
985 
986  // if the function didn't have an '@' prepended OR if we are about to terminate
987  // catch the error
988  if ((error_reporting() & $err_no) || $terminate) {
989 
990  // Strip out the file path begining
991  $err_file = hide_system_root($err_file);
992  $err_msg = hide_system_root($err_msg);
993 
994  $err_name = get_error_name($err_no);
995 
996  // send a report to the system error log
997  if (ini_get('log_errors')) {
998  $text_msg = strip_tags(preg_replace(Array('/<br\\/?>/i', '/<p[^>]*>/i'), Array("\n", "\n\n"), $err_msg));
999  log_error($text_msg, $err_no, $err_file, $err_line, $this->error_log_file_name);
1000  }
1001 
1002  // if they haven't put tags in the err msg assume it to be plain text
1003  $err_msg = strip_tags(preg_replace(Array('/<br\\/?>/i', '/<p[^>]*>/i'), Array("\n", "\n\n"), $err_msg));
1004  $lines = explode("\n", $err_msg);
1005  $len = 7 + strlen($err_file);
1006  $len = max($len, 7 + strlen($err_line));
1007  foreach ($lines as $line) {
1008  $len = max($len, strlen($line));
1009  }
1010  $len += 2;
1011  $str = '+'.str_repeat('-', $len)."+\n".
1012  '| '.$err_name.str_repeat(' ', $len - 2 - strlen($err_name))." |\n".
1013  '|'.str_repeat('-', $len)."|\n".
1014  '| File : '.$err_file.str_repeat(' ', $len - 9 - strlen($err_file))." |\n".
1015  '| Line : '.$err_line.str_repeat(' ', $len - 9 - strlen($err_line))." |\n".
1016  '|'.str_repeat('-', $len)."|\n";
1017  foreach ($lines as $line) {
1018  $str .= '| '.$line.str_repeat(' ', $len - 2 - strlen($line))." |\n";
1019  }
1020 
1021  $str .= '+'.str_repeat('-', $len)."+\n";
1022 
1023  if (!isset($this->_tmp['running_errors'])) {
1024  $this->_tmp['running_errors'] = '';
1025  }
1026  $this->_tmp['running_errors'] .= $str;
1027 
1028  }//end if error_reporting
1029 
1030  // if this is an irrercoverable error, send a distress email, delete ourselves and die
1031  if ($terminate && empty($this->_tmp['terminate_error'])) {
1032  $this->_tmp['terminate_error'] = TRUE; // stop possible recursion
1033 
1034  $root_user = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('root_user');
1035  $sys_admins = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('system_user_group');
1036 
1037  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
1038  $msg = $ms->newMessage();
1039  $msg_reps = Array(
1040  'system_name' => SQ_CONF_SYSTEM_NAME,
1041  'error_log_path' => SQ_LOG_PATH.'/'.$this->error_log_file_name.SQ_CONF_LOG_EXTENSION,
1042  'root_urls' => str_replace("\n", ',', SQ_CONF_SYSTEM_ROOT_URLS),
1043  );
1044  $msg->replacements = $msg_reps;
1045 
1046  // userid zero sends a msg to SQ_CONF_DEFAULT_EMAIL and SQ_CONF_TECH_EMAIL
1047  $msg->to = Array(0, $root_user->id, $sys_admins->id);
1048  $msg->from = 0; // a system message
1049  $msg->type = 'cron.term';
1050 
1051  $msg->send();
1052 
1053  exit(1);
1054 
1055  }//end if
1056 
1057  }//end _errorHandler()
1058 
1059 
1066  function getLastRun() {
1067  return (int) $this->attr('last_run');
1068  }
1069 
1070 }//end class
1071 
1072 ?>