]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/Schedulers/Scheduler.php
Release 6.3.0beta1
[Github/sugarcrm.git] / modules / Schedulers / Scheduler.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38 /*********************************************************************************
39
40  * Description:
41  ********************************************************************************/
42
43 class Scheduler extends SugarBean {
44         // table columns
45         var $id;
46         var $deleted;
47         var $date_entered;
48         var $date_modified;
49         var $modified_user_id;
50         var $created_by;
51         var $created_by_name;
52         var $modified_by_name;
53         var $name;
54         var $job;
55         var $date_time_start;
56         var $date_time_end;
57         var $job_interval;
58         var $time_from;
59         var $time_to;
60         var $last_run;
61         var $status;
62         var $catch_up;
63         // object attributes
64         var $user;
65         var $intervalParsed;
66         var $intervalHumanReadable;
67         var $metricsVar;
68         var $metricsVal;
69         var $dayInt;
70         var $dayLabel;
71         var $monthsInt;
72         var $monthsLabel;
73         var $suffixArray;
74         var $datesArray;
75         var $scheduledJobs;
76         var $timeOutMins = 60;
77         // standard SugarBean attrs
78         var $table_name                         = "schedulers";
79         var $object_name                        = "schedulers";
80         var $module_dir                         = "Schedulers";
81         var $new_schema                         = true;
82         var $process_save_dates         = true;
83         var $order_by;
84
85
86         function Scheduler($init=true) {
87                 parent::SugarBean();
88
89                 if($init) {
90
91                         $user = new User();
92                         $user->retrieve('1'); // Scheduler jobs run as Admin
93                         $this->user = $user;
94                 }
95         }
96
97
98         ///////////////////////////////////////////////////////////////////////////
99         ////    SCHEDULER HELPER FUNCTIONS
100         /**
101          * executes Scheduled job
102          */
103         function fire() {
104                 if(empty($this->job)) { // only execute when valid
105                         $GLOBALS['log']->fatal('Scheduler tried to fire an empty job!!');
106                         return false;
107                 }
108
109                 $exJob = explode('::', $this->job);
110                 if(is_array($exJob)) {
111                         // instantiate a new SchedulersJob object and prep it
112
113
114                         $trackerManager = TrackerManager::getInstance();
115                         $trackerManager->pause();
116                         $job                            = new SchedulersJob();
117                         $job->scheduler_id      = $this->id;
118                         $job->scheduler         = $this;
119                         $job->execute_time      = $job->handleDateFormat('now');
120                         $jobId = $job->save();
121                         $trackerManager->unPause();
122                         $job->retrieve($jobId);
123
124                         if($exJob[0] == 'function') {
125                                 $GLOBALS['log']->debug('----->Scheduler found a job of type FUNCTION');
126                                 require_once('modules/Schedulers/_AddJobsHere.php');
127
128                                 $job->setJobFlag(1);
129
130                                 $func = $exJob[1];
131                                 $GLOBALS['log']->debug('----->SchedulersJob firing '.$func);
132
133                                 $res = call_user_func($func);
134                                 if($res) {
135                                         $job->setJobFlag(2);
136                                         $job->finishJob();
137                                         return true;
138                                 } else {
139                                         $job->setJobFlag(3);
140                                         return false;
141                                 }
142                         } elseif($exJob[0] == 'url') {
143                                 if(function_exists('curl_init')) {
144                                         $GLOBALS['log']->debug('----->SchedulersJob found a job of type URL');
145                                         $job->setJobFlag(1);
146
147                                         $GLOBALS['log']->debug('----->SchedulersJob firing URL job: '.$exJob[1]);
148                                         if($job->fireUrl($exJob[1])) {
149                                                 $job->setJobFlag(2);
150                                                 $job->finishJob();
151                                                 return true;
152                                         } else {
153                                                 $job->setJobFlag(3);
154                                                 return false;
155                                         }
156                                 } else {
157                                         $job->setJobFlag(4);
158                                         return false;
159                                 }
160                         }
161                 }
162                 return false;
163         }
164
165         /**
166          * flushes dead or hung jobs
167          */
168         function flushDeadJobs() {
169                 $GLOBALS['log']->debug('-----> Scheduler flushing dead jobs');
170
171                 $lowerLimit = mktime(0, 0, 0, 1, 1, 2005); // jan 01, 2005, GMT-0
172                 $now = TimeDate::getInstance()->getNow()->ts; // current timestamp
173
174                 $q = "  SELECT s.id, s.name FROM schedulers s WHERE s.deleted=0 AND s.status = 'In Progress'";
175                 $r = $this->db->query($q);
176
177                 if($r != null) {
178                         while($a = $this->db->fetchByAssoc($r)) {
179                                 $q2 = " SELECT st.id, st.execute_time FROM schedulers_times st
180                                                 WHERE st.deleted=0
181                                                 AND st.scheduler_id = '{$a['id']}'
182                                                 ORDER BY st.execute_time DESC";
183                                 $r2 = $this->db->query($q2);
184                                 if($r2 != null) {
185                                         $a2 = $this->db->fetchByAssoc($r2); // we only care about the newest
186                     $a2['execute_time'] = $this->db->fromConvert($a2['execute_time'],'datetime');
187                                         if($a2 != null) {
188                                                 $GLOBALS['log']->debug("-----> Scheduler found [ {$a['name']} ] 'In Progress' with most recent Execute Time at [ {$a2['execute_time']} GMT-0 ]");
189
190                                                 $execTime = TimeDate::getInstance()->fromDB($a2['execute_time'])->ts;
191                         
192                         $GLOBALS['log']->debug("-----> Checking if Scheduler execTime ($execTime) is higher than lowerLimit ($lowerLimit)");
193
194                                                 if($execTime > $lowerLimit) {
195                                                         if(($now - $execTime) >= (60 * $this->timeOutMins)) {
196                                                                 $GLOBALS['log']->info("-----> Scheduler found a dead Job.  Flushing status and reseting Job");
197                                                                 $q3 = "UPDATE schedulers SET status = 'Active' WHERE id = '{$a['id']}'";
198                                                                 $this->db->query($q3);
199
200                                                                 $GLOBALS['log']->info("-----> Scheduler setting Job Instance status to 'failed'");
201                                                                 $q4 = "UPDATE schedulers_times SET status = 'failed' WHERE id = '{$a2['id']}';";
202                                                                 $this->db->query($q4);
203                                                         } else {
204                                                                 $GLOBALS['log']->debug("-----> Scheduler will wait for job to complete - not past threshold of [ ".($this->timeOutMins * 60)."secs ] - timeDiff is ".($now - $execTime)." secs");
205                                                         }
206                                                 } else {
207                                                         $GLOBALS['log']->fatal("-----> Scheduler got a bad execute time:        [ {$a2['execute_time']} GMT-0 ]");
208                                                 }
209
210                                         }
211                                 }
212                         }
213                 } // if
214         }
215
216         /**
217          * calculates if a job is qualified to run
218          */
219         function fireQualified() {
220                 if(empty($this->id)) { // execute only if we have an instance
221                         $GLOBALS['log']->fatal('Scheduler called fireQualified() in a non-instance');
222                         return false;
223                 }
224
225                 $now = TimeDate::getInstance()->getNow();
226                 $now = $now->setTime($now->hour, $now->min, "00")->asDb();
227                 $validTimes = $this->deriveDBDateTimes($this);
228
229                 if(is_array($validTimes) && in_array($now, $validTimes)) {
230                         $GLOBALS['log']->debug('----->Scheduler found valid job ('.$this->name.') for time GMT('.$now.')');
231                         return true;
232                 } else {
233                         $GLOBALS['log']->debug('----->Scheduler did NOT find valid job ('.$this->name.') for time GMT('.$now.')');
234                         return false;
235                 }
236         }
237
238         /**
239          * Checks if any jobs qualify to run at this moment
240          */
241         function checkPendingJobs() {
242                 $this->cleanJobLog();
243                 $allSchedulers = $this->get_full_list('', 'schedulers.status=\'Active\'');
244
245                 $GLOBALS['log']->info('-----> Scheduler found [ '.count($allSchedulers).' ] ACTIVE jobs');
246
247                 if(!empty($allSchedulers)) {
248                         foreach($allSchedulers as $focus) {
249                                 if($focus->fireQualified()) {
250                                         if($focus->fire()) {
251                                                 $GLOBALS['log']->debug('----->Scheduler Job completed successfully');
252                                         } else {
253                                                 $GLOBALS['log']->fatal('----->Scheduler Job FAILED');
254                                         }
255                                 }
256                         }
257                 } else {
258                         $GLOBALS['log']->debug('----->No Schedulers found');
259                 }
260         }
261
262         /**
263          * This function takes a Scheduler object and uses its job_interval
264          * attribute to derive DB-standard datetime strings, as many as are
265          * qualified by its ranges.  The times are from the time of calling the
266          * script.
267          *
268          * @param       $focus          Scheduler object
269          * @return      $dateTimes      array loaded with DB datetime strings derived from
270          *                                              the      job_interval attribute
271          * @return      false           If we the Scheduler is not in scope, return false.
272          */
273         function deriveDBDateTimes($focus) {
274         global $timedate;
275                 $GLOBALS['log']->debug('----->Schedulers->deriveDBDateTimes() got an object of type: '.$focus->object_name);
276                 /* [min][hr][dates][mon][days] */
277                 $dateTimes = array();
278                 $ints   = explode('::', str_replace(' ','',$focus->job_interval));
279                 $days   = $ints[4];
280                 $mons   = $ints[3];
281                 $dates  = $ints[2];
282                 $hrs    = $ints[1];
283                 $mins   = $ints[0];
284                 $today  = getdate($timedate->getNow()->ts);
285
286                 // derive day part
287                 if($days == '*') {
288                         $GLOBALS['log']->debug('----->got * day');
289
290                 } elseif(strstr($days, '*/')) {
291                         // the "*/x" format is nonsensical for this field
292                         // do basically nothing.
293                         $theDay = str_replace('*/','',$days);
294                         $dayName[] = $theDay;
295                 } elseif($days != '*') { // got particular day(s)
296                         if(strstr($days, ',')) {
297                                 $exDays = explode(',',$days);
298                                 foreach($exDays as $k1 => $dayGroup) {
299                                         if(strstr($dayGroup,'-')) {
300                                                 $exDayGroup = explode('-', $dayGroup); // build up range and iterate through
301                                                 for($i=$exDayGroup[0];$i<=$exDayGroup[1];$i++) {
302                                                         $dayName[] = $i;
303                                                 }
304                                         } else { // individuals
305                                                 $dayName[] = $dayGroup;
306                                         }
307                                 }
308                         } elseif(strstr($days, '-')) {
309                                 $exDayGroup = explode('-', $days); // build up range and iterate through
310                                 for($i=$exDayGroup[0];$i<=$exDayGroup[1];$i++) {
311                                         $dayName[] = $i;
312                                 }
313                         } else {
314                                 $dayName[] = $days;
315                         }
316
317                         // check the day to be in scope:
318                         if(!in_array(($today['wday']+6)%7, $dayName)) {//$dayName starts from Monday, while $today['wday'] starts from Sunday
319                                 return false;
320                         }
321                 } else {
322                         return false;
323                 }
324
325
326                 // derive months part
327                 if($mons == '*') {
328                         $GLOBALS['log']->debug('----->got * months');
329                 } elseif(strstr($mons, '*/')) {
330                         $mult = str_replace('*/','',$mons);
331                         $startMon = $timedate->fromDb(date_time_start)->month;
332                         $startFrom = ($startMon % $mult);
333
334                         for($i=$startFrom;$i<=12;$i+$mult) {
335                                 $compMons[] = $i+$mult;
336                                 $i += $mult;
337                         }
338                         // this month is not in one of the multiplier months
339                         if(!in_array($today['mon'],$compMons)) {
340                                 return false;
341                         }
342                 } elseif($mons != '*') {
343                         if(strstr($mons,',')) { // we have particular (groups) of months
344                                 $exMons = explode(',',$mons);
345                                 foreach($exMons as $k1 => $monGroup) {
346                                         if(strstr($monGroup, '-')) { // we have a range of months
347                                                 $exMonGroup = explode('-',$monGroup);
348                                                 for($i=$exMonGroup[0];$i<=$exMonGroup[1];$i++) {
349                                                         $monName[] = $i;
350                                                 }
351                                         } else {
352                                                 $monName[] = $monGroup;
353                                         }
354                                 }
355                         } elseif(strstr($mons, '-')) {
356                                 $exMonGroup = explode('-', $mons);
357                                 for($i=$exMonGroup[0];$i<=$exMonGroup[1];$i++) {
358                                         $monName[] = $i;
359                                 }
360                         } else { // one particular month
361                                 $monName[] = $mons;
362                         }
363
364                         // check that particular months are in scope
365                         if(!in_array($today['mon'], $monName)) {
366                                 return false;
367                         }
368                 }
369
370                 // derive dates part
371                 if($dates == '*') {
372                         $GLOBALS['log']->debug('----->got * dates');
373                 } elseif(strstr($dates, '*/')) {
374                         $mult = str_replace('*/','',$dates);
375                         $startDate = $timedate->fromDb($focus->date_time_start)->day;
376                         $startFrom = ($startDate % $mult);
377
378                         for($i=$startFrom; $i<=31; $i+$mult) {
379                                 $dateName[] = str_pad(($i+$mult),2,'0',STR_PAD_LEFT);
380                                 $i += $mult;
381                         }
382
383                         if(!in_array($today['mday'], $dateName)) {
384                                 return false;
385                         }
386                 } elseif($dates != '*') {
387                         if(strstr($dates, ',')) {
388                                 $exDates = explode(',', $dates);
389                                 foreach($exDates as $k1 => $dateGroup) {
390                                         if(strstr($dateGroup, '-')) {
391                                                 $exDateGroup = explode('-', $dateGroup);
392                                                 for($i=$exDateGroup[0];$i<=$exDateGroup[1];$i++) {
393                                                         $dateName[] = $i;
394                                                 }
395                                         } else {
396                                                 $dateName[] = $dateGroup;
397                                         }
398                                 }
399                         } elseif(strstr($dates, '-')) {
400                                 $exDateGroup = explode('-', $dates);
401                                 for($i=$exDateGroup[0];$i<=$exDateGroup[1];$i++) {
402                                         $dateName[] = $i;
403                                 }
404                         } else {
405                                 $dateName[] = $dates;
406                         }
407
408                         // check that dates are in scope
409                         if(!in_array($today['mday'], $dateName)) {
410                                 return false;
411                         }
412                 }
413
414                 // derive hours part
415                 //$currentHour = gmdate('G');
416                 //$currentHour = date('G', strtotime('00:00'));
417                 if($hrs == '*') {
418                         $GLOBALS['log']->debug('----->got * hours');
419                         for($i=0;$i<24; $i++) {
420                                 $hrName[]=$i;
421                         }
422                 } elseif(strstr($hrs, '*/')) {
423                         $mult = str_replace('*/','',$hrs);
424                         for($i=0; $i<24; $i) { // weird, i know
425                                 $hrName[]=$i;
426                                 $i += $mult;
427                         }
428                 } elseif($hrs != '*') {
429                         if(strstr($hrs, ',')) {
430                                 $exHrs = explode(',',$hrs);
431                                 foreach($exHrs as $k1 => $hrGroup) {
432                                         if(strstr($hrGroup, '-')) {
433                                                 $exHrGroup = explode('-', $hrGroup);
434                                                 for($i=$exHrGroup[0];$i<=$exHrGroup[1];$i++) {
435                                                         $hrName[] = $i;
436                                                 }
437                                         } else {
438                                                 $hrName[] = $hrGroup;
439                                         }
440                                 }
441                         } elseif(strstr($hrs, '-')) {
442                                 $exHrs = explode('-', $hrs);
443                                 for($i=$exHrs[0];$i<=$exHrs[1];$i++) {
444                                         $hrName[] = $i;
445                                 }
446                         } else {
447                                 $hrName[] = $hrs;
448                         }
449                 }
450                 //_pp($hrName);
451                 // derive minutes
452                 //$currentMin = date('i');
453                 $currentMin = $timedate->getNow()->minute;
454                 if(substr($currentMin, 0, 1) == '0') {
455                         $currentMin = substr($currentMin, 1, 1);
456                 }
457                 if($mins == '*') {
458                         $GLOBALS['log']->debug('----->got * mins');
459                         for($i=0; $i<60; $i++) {
460                                 if(($currentMin + $i) > 59) {
461                                         $minName[] = ($i + $currentMin - 60);
462                                 } else {
463                                         $minName[] = ($i+$currentMin);
464                                 }
465                         }
466                 } elseif(strstr($mins,'*/')) {
467                         $mult = str_replace('*/','',$mins);
468                         $startMin = $timedate->fromDb($focus->date_time_start)->minute;
469                         $startFrom = ($startMin % $mult);
470                         for($i=$startFrom; $i<=59; $i) {
471                                 if(($currentMin + $i) > 59) {
472                                         $minName[] = ($i + $currentMin - 60);
473                                 } else {
474                                         $minName[] = ($i+$currentMin);
475                                 }
476                                 $i += $mult;
477                         }
478
479                 } elseif($mins != '*') {
480                         if(strstr($mins, ',')) {
481                                 $exMins = explode(',',$mins);
482                                 foreach($exMins as $k1 => $minGroup) {
483                                         if(strstr($minGroup, '-')) {
484                                                 $exMinGroup = explode('-', $minGroup);
485                                                 for($i=$exMinGroup[0]; $i<=$exMinGroup[1]; $i++) {
486                                                         $minName[] = $i;
487                                                 }
488                                         } else {
489                                                 $minName[] = $minGroup;
490                                         }
491                                 }
492                         } elseif(strstr($mins, '-')) {
493                                 $exMinGroup = explode('-', $mins);
494                                 for($i=$exMinGroup[0]; $i<=$exMinGroup[1]; $i++) {
495                                         $minName[] = $i;
496                                 }
497                         } else {
498                                 $minName[] = $mins;
499                         }
500                 }
501                 //_pp($minName);
502                 // prep some boundaries - these are not in GMT b/c gmt is a 24hour period, possibly bridging 2 local days
503                 if(empty($focus->time_from)  && empty($focus->time_to) ) {
504                         $timeFromTs = 0;
505                         $timeToTs = $timedate->getNow(true)->get('+1 day')->ts;
506                 } else {
507                     $tfrom = $timedate->fromDbType($focus->time_from, 'time');
508                         $timeFromTs = $timedate->getNow(true)->setTime($tfrom->hour, $tfrom->min)->ts;
509                     $tto = $timedate->fromDbType($focus->time_to, 'time');
510                         $timeToTs = $timedate->getNow(true)->setTime($tto->hour, $tto->min)->ts;
511                 }
512                 $timeToTs++;
513
514                 if(empty($focus->last_run)) {
515                         $lastRunTs = 0;
516                 } else {
517                         $lastRunTs = $timedate->fromDb($focus->last_run)->ts;
518                 }
519
520
521                 /**
522                  * initialize return array
523                  */
524                 $validJobTime = array();
525
526                 global $timedate;
527                 $timeStartTs = $timedate->fromDb($focus->date_time_start)->ts;
528                 if(!empty($focus->date_time_end)) { // do the same for date_time_end if not empty
529                         $timeEndTs = $timedate->fromDb($focus->date_time_end)->ts;
530                 } else {
531                         $timeEndTs = $timedate->getNow(true)->get('+1 day')->ts;
532 //                      $dateTimeEnd = '2020-12-31 23:59:59'; // if empty, set it to something ridiculous
533                 }
534                 $timeEndTs++;
535                 /*_pp('hours:'); _pp($hrName);_pp('mins:'); _pp($minName);*/
536                 $dateobj = $timedate->getNow();
537                 $nowTs = $dateobj->ts;
538         $GLOBALS['log']->debug(sprintf("Constraints: start: %s from: %s end: %s to: %s now: %s",
539             gmdate('Y-m-d H:i:s', $timeStartTs), gmdate('Y-m-d H:i:s', $timeFromTs), gmdate('Y-m-d H:i:s', $timeEndTs),
540             gmdate('Y-m-d H:i:s', $timeToTs), $timedate->nowDb()
541             ));
542 //              _pp('currentHour: '. $currentHour);
543 //              _pp('timeStartTs: '.date('r',$timeStartTs));
544 //              _pp('timeFromTs: '.date('r',$timeFromTs));
545 //              _pp('timeEndTs: '.date('r',$timeEndTs));
546 //              _pp('timeToTs: '.date('r',$timeToTs));
547 //              _pp('mktime: '.date('r',mktime()));
548 //              _pp('timeLastRun: '.date('r',$lastRunTs));
549 //
550 //              _pp('hours: ');
551 //              _pp($hrName);
552 //              _pp('mins: ');
553 //              _ppd($minName);
554                 foreach($hrName as $kHr=>$hr) {
555                         foreach($minName as $kMin=>$min) {
556                             $timedate->tzUser($dateobj);
557                         $dateobj->setTime($hr, $min, 0);
558                         $tsGmt = $dateobj->ts;
559
560                                 if( $tsGmt >= $timeStartTs ) { // start is greater than the date specified by admin
561                                         if( $tsGmt >= $timeFromTs ) { // start is greater than the time_to spec'd by admin
562                         if($tsGmt > $lastRunTs) { // start from last run, last run should not be included
563                             if( $tsGmt <= $timeEndTs ) { // this is taken care of by the initial query - start is less than the date spec'd by admin
564                                 if( $tsGmt <= $timeToTs ) { // start is less than the time_to
565                                     $validJobTime[] = $dateobj->asDb();
566                                 } else {
567                                     //_pp('Job Time is NOT smaller that TimeTO: '.$tsGmt .'<='. $timeToTs);
568                                 }
569                             } else {
570                                 //_pp('Job Time is NOT smaller that DateTimeEnd: '.date('Y-m-d H:i:s',$tsGmt) .'<='. $dateTimeEnd); //_pp( $tsGmt .'<='. $timeEndTs );
571                             }
572                         }
573                                         } else {
574                                                 //_pp('Job Time is NOT bigger that TimeFrom: '.$tsGmt .'>='. $timeFromTs);
575                                         }
576                                 } else {
577                                         //_pp('Job Time is NOT Bigger than DateTimeStart: '.date('Y-m-d H:i',$tsGmt) .'>='. $dateTimeStart);
578                                 }
579                         }
580                 }
581                 //_ppd($validJobTime);
582                 // need ascending order to compare oldest time to last_run
583                 sort($validJobTime);
584                 /**
585                  * If "Execute If Missed bit is set
586                  */
587         $now = TimeDate::getInstance()->getNow();
588                 $now = $now->setTime($now->hour, $now->min, "00")->asDb();
589         
590                 if($focus->catch_up == 1) {
591                         if($focus->last_run == null) {
592                                 // always "catch-up"
593                                 $validJobTime[] = $now;
594                         } else {
595                                 // determine what the interval in min/hours is
596                                 // see if last_run is in it
597                                 // if not, add NOW
598                 if(!empty($validJobTime) && ($focus->last_run < $validJobTime[0]) && ($now > $validJobTime[0])) {
599                                 // cn: empty() bug 5914;
600                                 //if(!empty) should be checked, becasue if a scheduler is defined to run every day 4pm, then after 4pm, and it runs as 4pm, the $validJobTime will be empty, and it should not catch up
601                                 //if $focus->last_run is the the day before yesterday,  it should run yesterday and tomorrow,  but it hadn't run yesterday, then it should catch up today. But today is already filtered out when doing date check before. The catch up will not work on this occasion. If the scheduler runs at least one time on each day, I think this bug can be avoided.
602                                         $validJobTime[] = $now;
603                                 }
604                         }
605                 }
606                 return $validJobTime;
607         }
608
609         function handleIntervalType($type, $value, $mins, $hours) {
610                 global $mod_strings;
611                 /* [0]:min [1]:hour [2]:day of month [3]:month [4]:day of week */
612                 $days = array ( 0 => $mod_strings['LBL_MON'],
613                                                 1 => $mod_strings['LBL_TUE'],
614                                                 2 => $mod_strings['LBL_WED'],
615                                                 3 => $mod_strings['LBL_THU'],
616                                                 4 => $mod_strings['LBL_FRI'],
617                                                 5 => $mod_strings['LBL_SAT'],
618                                                 6 => $mod_strings['LBL_SUN'],
619                                                 '*' => $mod_strings['LBL_ALL']);
620                 switch($type) {
621                         case 0: // minutes
622                                 if($value == '0') {
623                                         //return;
624                                         return trim($mod_strings['LBL_ON_THE']).$mod_strings['LBL_HOUR_SING'];
625                                 } elseif(!preg_match('/[^0-9]/', $hours) && !preg_match('/[^0-9]/', $value)) {
626                                         return;
627
628                                 } elseif(preg_match('/\*\//', $value)) {
629                                         $value = str_replace('*/','',$value);
630                                         return $value.$mod_strings['LBL_MINUTES'];
631                                 } elseif(!preg_match('[^0-9]', $value)) {
632                                         return $mod_strings['LBL_ON_THE'].$value.$mod_strings['LBL_MIN_MARK'];
633                                 } else {
634                                         return $value;
635                                 }
636                         case 1: // hours
637                                 global $current_user;
638                                 if(preg_match('/\*\//', $value)) { // every [SOME INTERVAL] hours
639                                         $value = str_replace('*/','',$value);
640                                         return $value.$mod_strings['LBL_HOUR'];
641                                 } elseif(preg_match('/[^0-9]/', $mins)) { // got a range, or multiple of mins, so we return an 'Hours' label
642                                         return $value;
643                                 } else {        // got a "minutes" setting, so it will be at some o'clock.
644                                         $datef = $current_user->getUserDateTimePreferences();
645                                         return date($datef['time'], strtotime($value.':'.str_pad($mins, 2, '0', STR_PAD_LEFT)));
646                                 }
647                         case 2: // day of month
648                                 if(preg_match('/\*/', $value)) {
649                                         return $value;
650                                 } else {
651                                         return date('jS', strtotime('December '.$value));
652                                 }
653
654                         case 3: // months
655                                 return date('F', strtotime('2005-'.$value.'-01'));
656                         case 4: // days of week
657                                 return $days[$value];
658                         default:
659                                 return 'bad'; // no condition to touch this branch
660                 }
661         }
662
663         function setIntervalHumanReadable() {
664                 global $current_user;
665                 global $mod_strings;
666
667                 /* [0]:min [1]:hour [2]:day of month [3]:month [4]:day of week */
668                 $ints = $this->intervalParsed;
669                 $intVal = array('-', ',');
670                 $intSub = array($mod_strings['LBL_RANGE'], $mod_strings['LBL_AND']);
671                 $intInt = array(0 => $mod_strings['LBL_MINS'], 1 => $mod_strings['LBL_HOUR']);
672                 $tempInt = '';
673                 $iteration = '';
674
675                 foreach($ints['raw'] as $key => $interval) {
676                         if($tempInt != $iteration) {
677                                 $tempInt .= '; ';
678                         }
679                         $iteration = $tempInt;
680
681                         if($interval != '*' && $interval != '*/1') {
682                                 if(false !== strpos($interval, ',')) {
683                                         $exIndiv = explode(',', $interval);
684                                         foreach($exIndiv as $val) {
685                                                 if(false !== strpos($val, '-')) {
686                                                         $exRange = explode('-', $val);
687                                                         foreach($exRange as $valRange) {
688                                                                 if($tempInt != '') {
689                                                                         $tempInt .= $mod_strings['LBL_AND'];
690                                                                 }
691                                                                 $tempInt .= $this->handleIntervalType($key, $valRange, $ints['raw'][0], $ints['raw'][1]);
692                                                         }
693                                                 } elseif($tempInt != $iteration) {
694                                                         $tempInt .= $mod_strings['LBL_AND'];
695                                                 }
696                                                 $tempInt .= $this->handleIntervalType($key, $val, $ints['raw'][0], $ints['raw'][1]);
697                                         }
698                                 } elseif(false !== strpos($interval, '-')) {
699                                         $exRange = explode('-', $interval);
700                                         $tempInt .= $mod_strings['LBL_FROM'];
701                                         $check = $tempInt;
702
703                                         foreach($exRange as $val) {
704                                                 if($tempInt == $check) {
705                                                         $tempInt .= $this->handleIntervalType($key, $val, $ints['raw'][0], $ints['raw'][1]);
706                                                         $tempInt .= $mod_strings['LBL_RANGE'];
707
708                                                 } else {
709                                                         $tempInt .= $this->handleIntervalType($key, $val, $ints['raw'][0], $ints['raw'][1]);
710                                                 }
711                                         }
712
713                                 } elseif(false !== strpos($interval, '*/')) {
714                                         $tempInt .= $mod_strings['LBL_EVERY'];
715                                         $tempInt .= $this->handleIntervalType($key, $interval, $ints['raw'][0], $ints['raw'][1]);
716                                 } else {
717                                         $tempInt .= $this->handleIntervalType($key, $interval, $ints['raw'][0], $ints['raw'][1]);
718                                 }
719                         }
720                 } // end foreach()
721
722                 if($tempInt == '') {
723                         $this->intervalHumanReadable = $mod_strings['LBL_OFTEN'];
724                 } else {
725                         $tempInt = trim($tempInt);
726                         if(';' == substr($tempInt, (strlen($tempInt)-1), strlen($tempInt))) {
727                                 $tempInt = substr($tempInt, 0, (strlen($tempInt)-1));
728                         }
729                         $this->intervalHumanReadable = $tempInt;
730                 }
731         }
732
733
734         /* take an integer and return its suffix */
735         function setStandardArraysAttributes() {
736                 global $mod_strings;
737                 global $app_list_strings; // using from month _dom list
738
739                 $suffArr = array('','st','nd','rd');
740                 for($i=1; $i<32; $i++) {
741                         if($i > 3 && $i < 21) {
742                                 $this->suffixArray[$i] = $i."th";
743                         } elseif (substr($i,-1,1) < 4 && substr($i,-1,1) > 0) {
744                                 $this->suffixArray[$i] = $i.$suffArr[substr($i,-1,1)];
745                         } else {
746                                 $this->suffixArray[$i] = $i."th";
747                         }
748                         $this->datesArray[$i] = $i;
749                 }
750
751                 $this->dayInt = array('*',1,2,3,4,5,6,7);
752                 $this->dayLabel = array('*',$mod_strings['LBL_MON'],$mod_strings['LBL_TUE'],$mod_strings['LBL_WED'],$mod_strings['LBL_THU'],$mod_strings['LBL_FRI'],$mod_strings['LBL_SAT'],$mod_strings['LBL_SUN']);
753                 $this->monthsInt = array(0,1,2,3,4,5,6,7,8,9,10,11,12);
754                 $this->monthsLabel = $app_list_strings['dom_cal_month_long'];
755                 $this->metricsVar = array("*", "/", "-", ",");
756                 $this->metricsVal = array(' every ','',' thru ',' and ');
757         }
758
759         /**
760          *  takes the serialized interval string and renders it into an array
761          */
762         function parseInterval() {
763                 global $metricsVar;
764                 $ws = array(' ', '\r','\t');
765                 $blanks = array('','','');
766
767                 $intv = $this->job_interval;
768                 $rawValues = explode('::', $intv);
769                 $rawProcessed = str_replace($ws,$blanks,$rawValues); // strip all whitespace
770
771                 $hours = $rawValues[1].':::'.$rawValues[0];
772                 $months = $rawValues[3].':::'.$rawValues[2];
773
774                 $intA = array ( 'raw' => $rawProcessed,
775                                                 'hours' => $hours,
776                                                 'months' => $months,
777                                                 );
778
779                 $this->intervalParsed = $intA;
780         }
781
782         /**
783          * soft-deletes all job logs older than 24 hours
784          */
785         function cleanJobLog() 
786         {
787                 $GLOBALS['log']->info('DELETE FROM schedulers_times WHERE date_entered < '.db_convert("'" . TimeDate::getInstance()->getNow()->get("-1 day")->asDb() . "'", 'datetime'));
788                 $this->db->query('DELETE FROM schedulers_times WHERE date_entered < '.db_convert("'" . TimeDate::getInstance()->getNow()->get("-1 day")->asDb() . "'", 'datetime'));
789         }
790
791         /**
792          * checks for cURL libraries
793          */
794         function checkCurl() {
795                 global $mod_strings;
796
797                 if(!function_exists('curl_init')) {
798                         echo '
799                         <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
800                                 <tr height="20">
801                                         <th width="25%" colspan="2"><slot>
802                                                 '.$mod_strings['LBL_WARN_CURL_TITLE'].'
803                                         </slot></td>
804                                 </tr>
805                                 <tr class="oddListRowS1" >
806                                         <td scope="row" valign=TOP width="20%"><slot>
807                                                 '.$mod_strings['LBL_WARN_CURL'].'
808                                         <td scope="row" valign=TOP width="80%"><slot>
809                                                 <span class=error>'.$mod_strings['LBL_WARN_NO_CURL'].'</span>
810                                         </slot></td>
811                                 </tr>
812                         </table>
813                         <br>';
814                 }
815         }
816
817         function displayCronInstructions() {
818                 global $mod_strings;
819                 global $sugar_config;
820                 $error = '';
821                 if (!isset($_SERVER['Path'])) {
822             $_SERVER['Path'] = getenv('Path');
823         }
824         if(is_windows()) {
825                         if(isset($_SERVER['Path']) && !empty($_SERVER['Path'])) { // IIS IUSR_xxx may not have access to Path or it is not set
826                                 if(!strpos($_SERVER['Path'], 'php')) {
827                                         $error = '<em>'.$mod_strings['LBL_NO_PHP_CLI'].'</em>';
828                                 }
829                         }
830                 } else {
831                         if(isset($_SERVER['Path']) && !empty($_SERVER['Path'])) { // some Linux servers do not make this available
832                                 if(!strpos($_SERVER['PATH'], 'php')) {
833                                         $error = '<em>'.$mod_strings['LBL_NO_PHP_CLI'].'</em>';
834                                 }
835                         }
836                 }
837
838
839
840                 if(is_windows()) {
841                         echo '<br>';
842                         echo '
843                                 <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
844                                 <tr height="20">
845                                         <th><slot>
846                                                 '.$mod_strings['LBL_CRON_INSTRUCTIONS_WINDOWS'].'
847                                         </slot></th>
848                                 </tr>
849                                 <tr class="evenListRowS1">
850                                         <td scope="row" valign="top" width="70%"><slot>
851                                                 '.$mod_strings['LBL_CRON_WINDOWS_DESC'].'<br>
852                                                 <b>cd '.realpath('./').'<br>
853                                                 php.exe -f cron.php</b>
854                                         </slot></td>
855                                 </tr>
856                         </table>';
857                 } else {
858                         echo '<br>';
859                         echo '
860                                 <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
861                                 <tr height="20">
862                                         <th><slot>
863                                                 '.$mod_strings['LBL_CRON_INSTRUCTIONS_LINUX'].'
864                                         </slot></th>
865                                 </tr>
866                                 <tr>
867                                         <td scope="row" valign=TOP class="oddListRowS1" bgcolor="#fdfdfd" width="70%"><slot>
868                                                 '.$mod_strings['LBL_CRON_LINUX_DESC'].'<br>
869                                                 <b>*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;
870                                                 cd '.realpath('./').'; php -f cron.php > /dev/null 2>&1</b>
871                                                 <br>'.$error.'
872                                         </slot></td>
873                                 </tr>
874                         </table>';
875                 }
876         }
877
878         /**
879          * Archives schedulers of the same functionality, then instantiates new
880          * ones.
881          */
882         function rebuildDefaultSchedulers() {
883                 global $mod_strings;
884                 // truncate scheduler-related tables
885                 $this->db->query('DELETE FROM schedulers');
886                 $this->db->query('DELETE FROM schedulers_times');
887
888
889         $sched3 = new Scheduler();
890         $sched3->name               = $mod_strings['LBL_OOTB_TRACKER'];
891         $sched3->job                = 'function::trimTracker';
892         $sched3->date_time_start    = create_date(2005,1,1) . ' ' . create_time(0,0,1);
893         $sched3->date_time_end      = create_date(2020,12,31) . ' ' . create_time(23,59,59);
894         $sched3->job_interval       = '0::2::1::*::*';
895         $sched3->status             = 'Active';
896         $sched3->created_by         = '1';
897         $sched3->modified_user_id   = '1';
898         $sched3->catch_up           = '1';
899         $sched3->save();
900                 $sched4 = new Scheduler();
901                 $sched4->name                           = $mod_strings['LBL_OOTB_IE'];
902                 $sched4->job                            = 'function::pollMonitoredInboxes';
903                 $sched4->date_time_start        = create_date(2005,1,1) . ' ' . create_time(0,0,1);
904                 $sched4->date_time_end          = create_date(2020,12,31) . ' ' . create_time(23,59,59);
905                 $sched4->job_interval           = '*::*::*::*::*';
906                 $sched4->status                         = 'Active';
907                 $sched4->created_by                     = '1';
908                 $sched4->modified_user_id       = '1';
909                 $sched4->catch_up                       = '0';
910                 $sched4->save();
911
912                 $sched5 = new Scheduler();
913                 $sched5->name                           = $mod_strings['LBL_OOTB_BOUNCE'];
914                 $sched5->job                            = 'function::pollMonitoredInboxesForBouncedCampaignEmails';
915                 $sched5->date_time_start        = create_date(2005,1,1) . ' ' . create_time(0,0,1);
916                 $sched5->date_time_end          = create_date(2020,12,31) . ' ' . create_time(23,59,59);
917                 $sched5->job_interval           = '0::2-6::*::*::*';
918                 $sched5->status                         = 'Active';
919                 $sched5->created_by                     = '1';
920                 $sched5->modified_user_id       = '1';
921                 $sched5->catch_up                       = '1';
922                 $sched5->save();
923
924                 $sched6 = new Scheduler();
925                 $sched6->name                           = $mod_strings['LBL_OOTB_CAMPAIGN'];
926                 $sched6->job                            = 'function::runMassEmailCampaign';
927                 $sched6->date_time_start        = create_date(2005,1,1) . ' ' . create_time(0,0,1);
928                 $sched6->date_time_end          = create_date(2020,12,31) . ' ' . create_time(23,59,59);
929                 $sched6->job_interval           = '0::2-6::*::*::*';
930                 $sched6->status                         = 'Active';
931                 $sched6->created_by                     = '1';
932                 $sched6->modified_user_id       = '1';
933                 $sched6->catch_up                       = '1';
934                 $sched6->save();
935
936
937         $sched7 = new Scheduler();
938         $sched7->name               = $mod_strings['LBL_OOTB_PRUNE'];
939         $sched7->job                = 'function::pruneDatabase';
940         $sched7->date_time_start    = create_date(2005,1,1) . ' ' . create_time(0,0,1);
941         $sched7->date_time_end      = create_date(2020,12,31) . ' ' . create_time(23,59,59);
942         $sched7->job_interval       = '0::4::1::*::*';
943         $sched7->status             = 'Inactive';
944         $sched7->created_by         = '1';
945         $sched7->modified_user_id   = '1';
946         $sched7->catch_up           = '0';
947         $sched7->save();
948
949
950
951
952
953         }
954
955         ////    END SCHEDULER HELPER FUNCTIONS
956         ///////////////////////////////////////////////////////////////////////////
957
958
959         ///////////////////////////////////////////////////////////////////////////
960         ////    STANDARD SUGARBEAN OVERRIDES
961         /**
962          * function overrides the one in SugarBean.php
963          */
964         function create_export_query($order_by, $where, $show_deleted = 0) {
965                 return $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted = 0);
966         }
967
968         /**
969          * function overrides the one in SugarBean.php
970          */
971
972         /**
973          * function overrides the one in SugarBean.php
974          */
975         function fill_in_additional_list_fields() {
976                 $this->fill_in_additional_detail_fields();
977         }
978
979         /**
980          * function overrides the one in SugarBean.php
981          */
982         function fill_in_additional_detail_fields() {
983     }
984
985         /**
986          * function overrides the one in SugarBean.php
987          */
988         function get_list_view_data(){
989                 global $mod_strings;
990                 $temp_array = $this->get_list_view_array();
991         $temp_array["ENCODED_NAME"]=$this->name;
992         $this->parseInterval();
993         $this->setIntervalHumanReadable();
994         $temp_array['JOB_INTERVAL'] = $this->intervalHumanReadable;
995         if($this->date_time_end == '2020-12-31 23:59' || $this->date_time_end == '') {
996                 $temp_array['DATE_TIME_END'] = $mod_strings['LBL_PERENNIAL'];
997         }
998         $this->created_by_name = get_assigned_user_name($this->created_by);
999                 $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
1000         return $temp_array;
1001
1002         }
1003
1004         /**
1005          * returns the bean name - overrides SugarBean's
1006          */
1007         function get_summary_text() {
1008                 return $this->name;
1009         }
1010         ////    END STANDARD SUGARBEAN OVERRIDES
1011         ///////////////////////////////////////////////////////////////////////////
1012 } // end class definition
1013 ?>