]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/Schedulers/Scheduler.php
Release 6.2.0beta4
[Github/sugarcrm.git] / modules / Schedulers / Scheduler.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM 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                                         if($a2 != null) {
187                                                 $GLOBALS['log']->debug("-----> Scheduler found [ {$a['name']} ] 'In Progress' with most recent Execute Time at [ {$a2['execute_time']} GMT-0 ]");
188
189                                                 $execTime = strtotime($a2['execute_time']);
190                                                 if($execTime > $lowerLimit) {
191                                                         if(($now - $execTime) >= (60 * $this->timeOutMins)) {
192                                                                 $GLOBALS['log']->info("-----> Scheduler found a dead Job.  Flushing status and reseting Job");
193                                                                 $q3 = "UPDATE schedulers SET status = 'Active' WHERE id = '{$a['id']}'";
194                                                                 $this->db->query($q3);
195
196                                                                 $GLOBALS['log']->info("-----> Scheduler setting Job Instance status to 'failed'");
197                                                                 $q4 = "UPDATE schedulers_times SET status = 'failed' WHERE id = '{$a2['id']}';";
198                                                                 $this->db->query($q4);
199                                                         } else {
200                                                                 $GLOBALS['log']->debug("-----> Scheduler will wait for job to complete - not past threshold of [ ".($this->timeOutMins * 60)."secs ] - timeDiff is ".($now - $execTime)." secs");
201                                                         }
202                                                 } else {
203                                                         $GLOBALS['log']->fatal("-----> Scheduler got a bad execute time:        [ {$a2['execute_time']} GMT-0 ]");
204                                                 }
205
206                                         }
207                                 }
208                         }
209                 } // if
210         }
211
212         /**
213          * calculates if a job is qualified to run
214          */
215         function fireQualified() {
216                 if(empty($this->id)) { // execute only if we have an instance
217                         $GLOBALS['log']->fatal('Scheduler called fireQualified() in a non-instance');
218                         return false;
219                 }
220
221                 $now = TimeDate::getInstance()->getNow();
222                 $now = $now->setTime($now->hour, $now->min)->asDb();
223                 $validTimes = $this->deriveDBDateTimes($this);
224
225                 if(is_array($validTimes) && in_array($now, $validTimes)) {
226                         $GLOBALS['log']->debug('----->Scheduler found valid job ('.$this->name.') for time GMT('.$now.')');
227                         return true;
228                 } else {
229                         $GLOBALS['log']->debug('----->Scheduler did NOT find valid job ('.$this->name.') for time GMT('.$now.')');
230                         return false;
231                 }
232         }
233
234         /**
235          * Checks if any jobs qualify to run at this moment
236          */
237         function checkPendingJobs() {
238                 $this->cleanJobLog();
239                 $allSchedulers = $this->get_full_list('', 'schedulers.status=\'Active\'');
240
241                 $GLOBALS['log']->info('-----> Scheduler found [ '.count($allSchedulers).' ] ACTIVE jobs');
242
243                 if(!empty($allSchedulers)) {
244                         foreach($allSchedulers as $focus) {
245                                 if($focus->fireQualified()) {
246                                         if($focus->fire()) {
247                                                 $GLOBALS['log']->debug('----->Scheduler Job completed successfully');
248                                         } else {
249                                                 $GLOBALS['log']->fatal('----->Scheduler Job FAILED');
250                                         }
251                                 }
252                         }
253                 } else {
254                         $GLOBALS['log']->debug('----->No Schedulers found');
255                 }
256         }
257
258         /**
259          * This function takes a Scheduler object and uses its job_interval
260          * attribute to derive DB-standard datetime strings, as many as are
261          * qualified by its ranges.  The times are from the time of calling the
262          * script.
263          *
264          * @param       $focus          Scheduler object
265          * @return      $dateTimes      array loaded with DB datetime strings derived from
266          *                                              the      job_interval attribute
267          * @return      false           If we the Scheduler is not in scope, return false.
268          */
269         function deriveDBDateTimes($focus) {
270         global $timedate;
271                 $GLOBALS['log']->debug('----->Schedulers->deriveDBDateTimes() got an object of type: '.$focus->object_name);
272                 /* [min][hr][dates][mon][days] */
273                 $dateTimes = array();
274                 $ints   = explode('::', str_replace(' ','',$focus->job_interval));
275                 $days   = $ints[4];
276                 $mons   = $ints[3];
277                 $dates  = $ints[2];
278                 $hrs    = $ints[1];
279                 $mins   = $ints[0];
280                 $today  = getdate(TimeDate::getInstance()->getNow()->ts);
281
282                 // derive day part
283                 if($days == '*') {
284                         $GLOBALS['log']->debug('----->got * day');
285
286                 } elseif(strstr($days, '*/')) {
287                         // the "*/x" format is nonsensical for this field
288                         // do basically nothing.
289                         $theDay = str_replace('*/','',$days);
290                         $dayName[] = $theDay;
291                 } elseif($days != '*') { // got particular day(s)
292                         if(strstr($days, ',')) {
293                                 $exDays = explode(',',$days);
294                                 foreach($exDays as $k1 => $dayGroup) {
295                                         if(strstr($dayGroup,'-')) {
296                                                 $exDayGroup = explode('-', $dayGroup); // build up range and iterate through
297                                                 for($i=$exDayGroup[0];$i<=$exDayGroup[1];$i++) {
298                                                         $dayName[] = $i;
299                                                 }
300                                         } else { // individuals
301                                                 $dayName[] = $dayGroup;
302                                         }
303                                 }
304                         } elseif(strstr($days, '-')) {
305                                 $exDayGroup = explode('-', $days); // build up range and iterate through
306                                 for($i=$exDayGroup[0];$i<=$exDayGroup[1];$i++) {
307                                         $dayName[] = $i;
308                                 }
309                         } else {
310                                 $dayName[] = $days;
311                         }
312
313                         // check the day to be in scope:
314                         if(!in_array(($today['wday']+6)%7, $dayName)) {//$dayName starts from Monday, while $today['wday'] starts from Sunday
315                                 return false;
316                         }
317                 } else {
318                         return false;
319                 }
320
321
322                 // derive months part
323                 if($mons == '*') {
324                         $GLOBALS['log']->debug('----->got * months');
325                 } elseif(strstr($mons, '*/')) {
326                         $mult = str_replace('*/','',$mons);
327                         $startMon = $timedate->fromDb(date_time_start)->month;
328                         $startFrom = ($startMon % $mult);
329
330                         for($i=$startFrom;$i<=12;$i+$mult) {
331                                 $compMons[] = $i+$mult;
332                                 $i += $mult;
333                         }
334                         // this month is not in one of the multiplier months
335                         if(!in_array($today['mon'],$compMons)) {
336                                 return false;
337                         }
338                 } elseif($mons != '*') {
339                         if(strstr($mons,',')) { // we have particular (groups) of months
340                                 $exMons = explode(',',$mons);
341                                 foreach($exMons as $k1 => $monGroup) {
342                                         if(strstr($monGroup, '-')) { // we have a range of months
343                                                 $exMonGroup = explode('-',$monGroup);
344                                                 for($i=$exMonGroup[0];$i<=$exMonGroup[1];$i++) {
345                                                         $monName[] = $i;
346                                                 }
347                                         } else {
348                                                 $monName[] = $monGroup;
349                                         }
350                                 }
351                         } elseif(strstr($mons, '-')) {
352                                 $exMonGroup = explode('-', $mons);
353                                 for($i=$exMonGroup[0];$i<=$exMonGroup[1];$i++) {
354                                         $monName[] = $i;
355                                 }
356                         } else { // one particular month
357                                 $monName[] = $mons;
358                         }
359
360                         // check that particular months are in scope
361                         if(!in_array($today['mon'], $monName)) {
362                                 return false;
363                         }
364                 }
365
366                 // derive dates part
367                 if($dates == '*') {
368                         $GLOBALS['log']->debug('----->got * dates');
369                 } elseif(strstr($dates, '*/')) {
370                         $mult = str_replace('*/','',$dates);
371                         $startDate = $timedate->fromDb($focus->date_time_start)->day;
372                         $startFrom = ($startDate % $mult);
373
374                         for($i=$startFrom; $i<=31; $i+$mult) {
375                                 $dateName[] = str_pad(($i+$mult),2,'0',STR_PAD_LEFT);
376                                 $i += $mult;
377                         }
378
379                         if(!in_array($today['mday'], $dateName)) {
380                                 return false;
381                         }
382                 } elseif($dates != '*') {
383                         if(strstr($dates, ',')) {
384                                 $exDates = explode(',', $dates);
385                                 foreach($exDates as $k1 => $dateGroup) {
386                                         if(strstr($dateGroup, '-')) {
387                                                 $exDateGroup = explode('-', $dateGroup);
388                                                 for($i=$exDateGroup[0];$i<=$exDateGroup[1];$i++) {
389                                                         $dateName[] = $i;
390                                                 }
391                                         } else {
392                                                 $dateName[] = $dateGroup;
393                                         }
394                                 }
395                         } elseif(strstr($dates, '-')) {
396                                 $exDateGroup = explode('-', $dates);
397                                 for($i=$exDateGroup[0];$i<=$exDateGroup[1];$i++) {
398                                         $dateName[] = $i;
399                                 }
400                         } else {
401                                 $dateName[] = $dates;
402                         }
403
404                         // check that dates are in scope
405                         if(!in_array($today['mday'], $dateName)) {
406                                 return false;
407                         }
408                 }
409
410                 // derive hours part
411                 //$currentHour = gmdate('G');
412                 //$currentHour = date('G', strtotime('00:00'));
413                 if($hrs == '*') {
414                         $GLOBALS['log']->debug('----->got * hours');
415                         for($i=0;$i<24; $i++) {
416                                 $hrName[]=$i;
417                         }
418                 } elseif(strstr($hrs, '*/')) {
419                         $mult = str_replace('*/','',$hrs);
420                         for($i=0; $i<24; $i) { // weird, i know
421                                 $hrName[]=$i;
422                                 $i += $mult;
423                         }
424                 } elseif($hrs != '*') {
425                         if(strstr($hrs, ',')) {
426                                 $exHrs = explode(',',$hrs);
427                                 foreach($exHrs as $k1 => $hrGroup) {
428                                         if(strstr($hrGroup, '-')) {
429                                                 $exHrGroup = explode('-', $hrGroup);
430                                                 for($i=$exHrGroup[0];$i<=$exHrGroup[1];$i++) {
431                                                         $hrName[] = $i;
432                                                 }
433                                         } else {
434                                                 $hrName[] = $hrGroup;
435                                         }
436                                 }
437                         } elseif(strstr($hrs, '-')) {
438                                 $exHrs = explode('-', $hrs);
439                                 for($i=$exHrs[0];$i<=$exHrs[1];$i++) {
440                                         $hrName[] = $i;
441                                 }
442                         } else {
443                                 $hrName[] = $hrs;
444                         }
445                 }
446                 //_pp($hrName);
447                 // derive minutes
448                 //$currentMin = date('i');
449                 $currentMin = $timedate->getNow()->minute;
450                 if(substr($currentMin, 0, 1) == '0') {
451                         $currentMin = substr($currentMin, 1, 1);
452                 }
453                 if($mins == '*') {
454                         $GLOBALS['log']->debug('----->got * mins');
455                         for($i=0; $i<60; $i++) {
456                                 if(($currentMin + $i) > 59) {
457                                         $minName[] = ($i + $currentMin - 60);
458                                 } else {
459                                         $minName[] = ($i+$currentMin);
460                                 }
461                         }
462                 } elseif(strstr($mins,'*/')) {
463                         $mult = str_replace('*/','',$mins);
464                         $startMin = $timedate->fromDb($focus->date_time_start)->minute;
465                         $startFrom = ($startMin % $mult);
466                         for($i=$startFrom; $i<=59; $i) {
467                                 if(($currentMin + $i) > 59) {
468                                         $minName[] = ($i + $currentMin - 60);
469                                 } else {
470                                         $minName[] = ($i+$currentMin);
471                                 }
472                                 $i += $mult;
473                         }
474
475                 } elseif($mins != '*') {
476                         if(strstr($mins, ',')) {
477                                 $exMins = explode(',',$mins);
478                                 foreach($exMins as $k1 => $minGroup) {
479                                         if(strstr($minGroup, '-')) {
480                                                 $exMinGroup = explode('-', $minGroup);
481                                                 for($i=$exMinGroup[0]; $i<=$exMinGroup[1]; $i++) {
482                                                         $minName[] = $i;
483                                                 }
484                                         } else {
485                                                 $minName[] = $minGroup;
486                                         }
487                                 }
488                         } elseif(strstr($mins, '-')) {
489                                 $exMinGroup = explode('-', $mins);
490                                 for($i=$exMinGroup[0]; $i<=$exMinGroup[1]; $i++) {
491                                         $minName[] = $i;
492                                 }
493                         } else {
494                                 $minName[] = $mins;
495                         }
496                 }
497                 //_pp($minName);
498                 // prep some boundaries - these are not in GMT b/c gmt is a 24hour period, possibly bridging 2 local days
499                 if(empty($focus->time_from)  && empty($focus->time_to) ) {
500                         $timeFromTs = 0;
501                         $timeToTs = strtotime('+1 day');
502                 } else {
503                         $timeFromTs = strtotime($focus->time_from);     // these are now GMT (timestamps are all GMT)
504                         $timeToTs       = strtotime($focus->time_to);   // see above
505                         if($timeFromTs > $timeToTs) { // we've crossed into the next day
506                                 $timeToTs = strtotime('+1 day '. $focus->time_to);      // also in GMT
507                         }
508                 }
509                 $timeToTs++;
510
511                 if(empty($focus->last_run)) {
512                         $lastRunTs = 0;
513                 } else {
514                         $lastRunTs = strtotime($focus->last_run . ' UTC');
515                 }
516
517
518                 /**
519                  * initialize return array
520                  */
521                 $validJobTime = array();
522
523                 global $timedate;
524                 $timeStartTs = strtotime($focus->date_time_start . ' UTC');
525                 if(!empty($focus->date_time_end)) { // do the same for date_time_end if not empty
526                         $dateTimeEnd = $focus->date_time_end;
527                 } else {
528                         $dateTimeEnd = $timedate->getNow()+get('+1 day')->asDb();
529 //                      $dateTimeEnd = '2020-12-31 23:59:59'; // if empty, set it to something ridiculous
530                 }
531                 $timeEndTs = strtotime($dateTimeEnd.' UTC'); // GMT end timestamp if necessary
532                 $timeEndTs++;
533                 /*_pp('hours:'); _pp($hrName);_pp('mins:'); _pp($minName);*/
534                 $nowTs = $timedate->getNow()->ts;
535
536 //              _pp('currentHour: '. $currentHour);
537 //              _pp('timeStartTs: '.date('r',$timeStartTs));
538 //              _pp('timeFromTs: '.date('r',$timeFromTs));
539 //              _pp('timeEndTs: '.date('r',$timeEndTs));
540 //              _pp('timeToTs: '.date('r',$timeToTs));
541 //              _pp('mktime: '.date('r',mktime()));
542 //              _pp('timeLastRun: '.date('r',$lastRunTs));
543 //
544 //              _pp('hours: ');
545 //              _pp($hrName);
546 //              _pp('mins: ');
547 //              _ppd($minName);
548                 $hourSeen = 0;
549                 foreach($hrName as $kHr=>$hr) {
550                         $hourSeen++;
551                         foreach($minName as $kMin=>$min) {
552                                 if($hourSeen == 25) {
553                                         $theDate = TimeDate::getInstance()->asDbDate(TimeDate::getInstance()->getNow()->get('+1 day'));
554                                 } else {
555                                         $theDate = TimeDate::getInstance()->nowDbDate();
556                                 }
557
558                                 $tsGmt = strtotime($theDate.' '.str_pad($hr,2,'0',STR_PAD_LEFT).":".str_pad($min,2,'0',STR_PAD_LEFT).":00"); // this is LOCAL
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[] = $timedate->asDb($timedate->fromTimestamp($tsGmt));
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                 if($focus->catch_up == 1) {
588                         if($focus->last_run == null) {
589                                 // always "catch-up"
590                                 $validJobTime[] = $timedate->nowDb();
591                         } else {
592                                 // determine what the interval in min/hours is
593                                 // see if last_run is in it
594                                 // if not, add NOW
595                 $now = $timedate->nowDb();
596                                 if(!empty($validJobTime) && ($focus->last_run < $validJobTime[0]) && ($now > $validJobTime[0])) {
597                                 // cn: empty() bug 5914;
598                                 //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
599                                 //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.
600                                         $validJobTime[] = $now;
601                                 }
602                         }
603                 }
604                 return $validJobTime;
605         }
606
607         function handleIntervalType($type, $value, $mins, $hours) {
608                 global $mod_strings;
609                 /* [0]:min [1]:hour [2]:day of month [3]:month [4]:day of week */
610                 $days = array ( 0 => $mod_strings['LBL_MON'],
611                                                 1 => $mod_strings['LBL_TUE'],
612                                                 2 => $mod_strings['LBL_WED'],
613                                                 3 => $mod_strings['LBL_THU'],
614                                                 4 => $mod_strings['LBL_FRI'],
615                                                 5 => $mod_strings['LBL_SAT'],
616                                                 6 => $mod_strings['LBL_SUN'],
617                                                 '*' => $mod_strings['LBL_ALL']);
618                 switch($type) {
619                         case 0: // minutes
620                                 if($value == '0') {
621                                         //return;
622                                         return trim($mod_strings['LBL_ON_THE']).$mod_strings['LBL_HOUR_SING'];
623                                 } elseif(!preg_match('/[^0-9]/', $hours) && !preg_match('/[^0-9]/', $value)) {
624                                         return;
625
626                                 } elseif(preg_match('/\*\//', $value)) {
627                                         $value = str_replace('*/','',$value);
628                                         return $value.$mod_strings['LBL_MINUTES'];
629                                 } elseif(!preg_match('[^0-9]', $value)) {
630                                         return $mod_strings['LBL_ON_THE'].$value.$mod_strings['LBL_MIN_MARK'];
631                                 } else {
632                                         return $value;
633                                 }
634                         case 1: // hours
635                                 global $current_user;
636                                 if(preg_match('/\*\//', $value)) { // every [SOME INTERVAL] hours
637                                         $value = str_replace('*/','',$value);
638                                         return $value.$mod_strings['LBL_HOUR'];
639                                 } elseif(preg_match('/[^0-9]/', $mins)) { // got a range, or multiple of mins, so we return an 'Hours' label
640                                         return $value;
641                                 } else {        // got a "minutes" setting, so it will be at some o'clock.
642                                         $datef = $current_user->getUserDateTimePreferences();
643                                         return date($datef['time'], strtotime($value.':'.str_pad($mins, 2, '0', STR_PAD_LEFT)));
644                                 }
645                         case 2: // day of month
646                                 if(preg_match('/\*/', $value)) {
647                                         return $value;
648                                 } else {
649                                         return date('jS', strtotime('December '.$value));
650                                 }
651
652                         case 3: // months
653                                 return date('F', strtotime('2005-'.$value.'-01'));
654                         case 4: // days of week
655                                 return $days[$value];
656                         default:
657                                 return 'bad'; // no condition to touch this branch
658                 }
659         }
660
661         function setIntervalHumanReadable() {
662                 global $current_user;
663                 global $mod_strings;
664
665                 /* [0]:min [1]:hour [2]:day of month [3]:month [4]:day of week */
666                 $ints = $this->intervalParsed;
667                 $intVal = array('-', ',');
668                 $intSub = array($mod_strings['LBL_RANGE'], $mod_strings['LBL_AND']);
669                 $intInt = array(0 => $mod_strings['LBL_MINS'], 1 => $mod_strings['LBL_HOUR']);
670                 $tempInt = '';
671                 $iteration = '';
672
673                 foreach($ints['raw'] as $key => $interval) {
674                         if($tempInt != $iteration) {
675                                 $tempInt .= '; ';
676                         }
677                         $iteration = $tempInt;
678
679                         if($interval != '*' && $interval != '*/1') {
680                                 if(false !== strpos($interval, ',')) {
681                                         $exIndiv = explode(',', $interval);
682                                         foreach($exIndiv as $val) {
683                                                 if(false !== strpos($val, '-')) {
684                                                         $exRange = explode('-', $val);
685                                                         foreach($exRange as $valRange) {
686                                                                 if($tempInt != '') {
687                                                                         $tempInt .= $mod_strings['LBL_AND'];
688                                                                 }
689                                                                 $tempInt .= $this->handleIntervalType($key, $valRange, $ints['raw'][0], $ints['raw'][1]);
690                                                         }
691                                                 } elseif($tempInt != $iteration) {
692                                                         $tempInt .= $mod_strings['LBL_AND'];
693                                                 }
694                                                 $tempInt .= $this->handleIntervalType($key, $val, $ints['raw'][0], $ints['raw'][1]);
695                                         }
696                                 } elseif(false !== strpos($interval, '-')) {
697                                         $exRange = explode('-', $interval);
698                                         $tempInt .= $mod_strings['LBL_FROM'];
699                                         $check = $tempInt;
700
701                                         foreach($exRange as $val) {
702                                                 if($tempInt == $check) {
703                                                         $tempInt .= $this->handleIntervalType($key, $val, $ints['raw'][0], $ints['raw'][1]);
704                                                         $tempInt .= $mod_strings['LBL_RANGE'];
705
706                                                 } else {
707                                                         $tempInt .= $this->handleIntervalType($key, $val, $ints['raw'][0], $ints['raw'][1]);
708                                                 }
709                                         }
710
711                                 } elseif(false !== strpos($interval, '*/')) {
712                                         $tempInt .= $mod_strings['LBL_EVERY'];
713                                         $tempInt .= $this->handleIntervalType($key, $interval, $ints['raw'][0], $ints['raw'][1]);
714                                 } else {
715                                         $tempInt .= $this->handleIntervalType($key, $interval, $ints['raw'][0], $ints['raw'][1]);
716                                 }
717                         }
718                 } // end foreach()
719
720                 if($tempInt == '') {
721                         $this->intervalHumanReadable = $mod_strings['LBL_OFTEN'];
722                 } else {
723                         $tempInt = trim($tempInt);
724                         if(';' == substr($tempInt, (strlen($tempInt)-1), strlen($tempInt))) {
725                                 $tempInt = substr($tempInt, 0, (strlen($tempInt)-1));
726                         }
727                         $this->intervalHumanReadable = $tempInt;
728                 }
729         }
730
731
732         /* take an integer and return its suffix */
733         function setStandardArraysAttributes() {
734                 global $mod_strings;
735                 global $app_list_strings; // using from month _dom list
736
737                 $suffArr = array('','st','nd','rd');
738                 for($i=1; $i<32; $i++) {
739                         if($i > 3 && $i < 21) {
740                                 $this->suffixArray[$i] = $i."th";
741                         } elseif (substr($i,-1,1) < 4 && substr($i,-1,1) > 0) {
742                                 $this->suffixArray[$i] = $i.$suffArr[substr($i,-1,1)];
743                         } else {
744                                 $this->suffixArray[$i] = $i."th";
745                         }
746                         $this->datesArray[$i] = $i;
747                 }
748
749                 $this->dayInt = array('*',1,2,3,4,5,6,7);
750                 $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']);
751                 $this->monthsInt = array(0,1,2,3,4,5,6,7,8,9,10,11,12);
752                 $this->monthsLabel = $app_list_strings['dom_cal_month_long'];
753                 $this->metricsVar = array("*", "/", "-", ",");
754                 $this->metricsVal = array(' every ','',' thru ',' and ');
755         }
756
757         /**
758          *  takes the serialized interval string and renders it into an array
759          */
760         function parseInterval() {
761                 global $metricsVar;
762                 $ws = array(' ', '\r','\t');
763                 $blanks = array('','','');
764
765                 $intv = $this->job_interval;
766                 $rawValues = explode('::', $intv);
767                 $rawProcessed = str_replace($ws,$blanks,$rawValues); // strip all whitespace
768
769                 $hours = $rawValues[1].':::'.$rawValues[0];
770                 $months = $rawValues[3].':::'.$rawValues[2];
771
772                 $intA = array ( 'raw' => $rawProcessed,
773                                                 'hours' => $hours,
774                                                 'months' => $months,
775                                                 );
776
777                 $this->intervalParsed = $intA;
778         }
779
780         /**
781          * soft-deletes all job logs older than 24 hours
782          */
783         function cleanJobLog() {
784                 $this->db->query('DELETE FROM schedulers_times WHERE date_entered < '.db_convert('\''.TimeDate::getInstance()->nowDb(), strtotime('-24 hours')).'\'', 'datetime'.'');
785         }
786
787         /**
788          * checks for cURL libraries
789          */
790         function checkCurl() {
791                 global $mod_strings;
792
793                 if(!function_exists('curl_init')) {
794                         echo '
795                         <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
796                                 <tr height="20">
797                                         <th width="25%" colspan="2"><slot>
798                                                 '.$mod_strings['LBL_WARN_CURL_TITLE'].'
799                                         </slot></td>
800                                 </tr>
801                                 <tr class="oddListRowS1" >
802                                         <td scope="row" valign=TOP width="20%"><slot>
803                                                 '.$mod_strings['LBL_WARN_CURL'].'
804                                         <td scope="row" valign=TOP width="80%"><slot>
805                                                 <span class=error>'.$mod_strings['LBL_WARN_NO_CURL'].'</span>
806                                         </slot></td>
807                                 </tr>
808                         </table>
809                         <br>';
810                 }
811         }
812
813         function displayCronInstructions() {
814                 global $mod_strings;
815                 global $sugar_config;
816                 $error = '';
817                 if (!isset($_SERVER['Path'])) {
818             $_SERVER['Path'] = getenv('Path');
819         }
820         if(is_windows()) {
821                         if(isset($_SERVER['Path']) && !empty($_SERVER['Path'])) { // IIS IUSR_xxx may not have access to Path or it is not set
822                                 if(!strpos($_SERVER['Path'], 'php')) {
823                                         $error = '<em>'.$mod_strings['LBL_NO_PHP_CLI'].'</em>';
824                                 }
825                         }
826                 } else {
827                         if(isset($_SERVER['Path']) && !empty($_SERVER['Path'])) { // some Linux servers do not make this available
828                                 if(!strpos($_SERVER['PATH'], 'php')) {
829                                         $error = '<em>'.$mod_strings['LBL_NO_PHP_CLI'].'</em>';
830                                 }
831                         }
832                 }
833
834
835
836                 if(is_windows()) {
837                         echo '<br>';
838                         echo '
839                                 <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
840                                 <tr height="20">
841                                         <th><slot>
842                                                 '.$mod_strings['LBL_CRON_INSTRUCTIONS_WINDOWS'].'
843                                         </slot></th>
844                                 </tr>
845                                 <tr class="evenListRowS1">
846                                         <td scope="row" valign="top" width="70%"><slot>
847                                                 '.$mod_strings['LBL_CRON_WINDOWS_DESC'].'<br>
848                                                 <b>cd '.realpath('./').'<br>
849                                                 php.exe -f cron.php</b>
850                                         </slot></td>
851                                 </tr>
852                         </table>';
853                 } else {
854                         echo '<br>';
855                         echo '
856                                 <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
857                                 <tr height="20">
858                                         <th><slot>
859                                                 '.$mod_strings['LBL_CRON_INSTRUCTIONS_LINUX'].'
860                                         </slot></th>
861                                 </tr>
862                                 <tr>
863                                         <td scope="row" valign=TOP class="oddListRowS1" bgcolor="#fdfdfd" width="70%"><slot>
864                                                 '.$mod_strings['LBL_CRON_LINUX_DESC'].'<br>
865                                                 <b>*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;
866                                                 cd '.realpath('./').'; php -f cron.php > /dev/null 2>&1</b>
867                                                 <br>'.$error.'
868                                         </slot></td>
869                                 </tr>
870                         </table>';
871                 }
872         }
873
874         /**
875          * Archives schedulers of the same functionality, then instantiates new
876          * ones.
877          */
878         function rebuildDefaultSchedulers() {
879                 global $mod_strings;
880                 // truncate scheduler-related tables
881                 $this->db->query('DELETE FROM schedulers');
882                 $this->db->query('DELETE FROM schedulers_times');
883
884
885         $sched3 = new Scheduler();
886         $sched3->name               = $mod_strings['LBL_OOTB_TRACKER'];
887         $sched3->job                = 'function::trimTracker';
888         $sched3->date_time_start    = create_date(2005,1,1) . ' ' . create_time(0,0,1);
889         $sched3->date_time_end      = create_date(2020,12,31) . ' ' . create_time(23,59,59);
890         $sched3->job_interval       = '0::2::1::*::*';
891         $sched3->status             = 'Active';
892         $sched3->created_by         = '1';
893         $sched3->modified_user_id   = '1';
894         $sched3->catch_up           = '1';
895         $sched3->save();
896                 $sched4 = new Scheduler();
897                 $sched4->name                           = $mod_strings['LBL_OOTB_IE'];
898                 $sched4->job                            = 'function::pollMonitoredInboxes';
899                 $sched4->date_time_start        = create_date(2005,1,1) . ' ' . create_time(0,0,1);
900                 $sched4->date_time_end          = create_date(2020,12,31) . ' ' . create_time(23,59,59);
901                 $sched4->job_interval           = '*::*::*::*::*';
902                 $sched4->status                         = 'Active';
903                 $sched4->created_by                     = '1';
904                 $sched4->modified_user_id       = '1';
905                 $sched4->catch_up                       = '0';
906                 $sched4->save();
907
908                 $sched5 = new Scheduler();
909                 $sched5->name                           = $mod_strings['LBL_OOTB_BOUNCE'];
910                 $sched5->job                            = 'function::pollMonitoredInboxesForBouncedCampaignEmails';
911                 $sched5->date_time_start        = create_date(2005,1,1) . ' ' . create_time(0,0,1);
912                 $sched5->date_time_end          = create_date(2020,12,31) . ' ' . create_time(23,59,59);
913                 $sched5->job_interval           = '0::2-6::*::*::*';
914                 $sched5->status                         = 'Active';
915                 $sched5->created_by                     = '1';
916                 $sched5->modified_user_id       = '1';
917                 $sched5->catch_up                       = '1';
918                 $sched5->save();
919
920                 $sched6 = new Scheduler();
921                 $sched6->name                           = $mod_strings['LBL_OOTB_CAMPAIGN'];
922                 $sched6->job                            = 'function::runMassEmailCampaign';
923                 $sched6->date_time_start        = create_date(2005,1,1) . ' ' . create_time(0,0,1);
924                 $sched6->date_time_end          = create_date(2020,12,31) . ' ' . create_time(23,59,59);
925                 $sched6->job_interval           = '0::2-6::*::*::*';
926                 $sched6->status                         = 'Active';
927                 $sched6->created_by                     = '1';
928                 $sched6->modified_user_id       = '1';
929                 $sched6->catch_up                       = '1';
930                 $sched6->save();
931
932
933         $sched7 = new Scheduler();
934         $sched7->name               = $mod_strings['LBL_OOTB_PRUNE'];
935         $sched7->job                = 'function::pruneDatabase';
936         $sched7->date_time_start    = create_date(2005,1,1) . ' ' . create_time(0,0,1);
937         $sched7->date_time_end      = create_date(2020,12,31) . ' ' . create_time(23,59,59);
938         $sched7->job_interval       = '0::4::1::*::*';
939         $sched7->status             = 'Inactive';
940         $sched7->created_by         = '1';
941         $sched7->modified_user_id   = '1';
942         $sched7->catch_up           = '0';
943         $sched7->save();
944
945
946
947
948
949         }
950
951         ////    END SCHEDULER HELPER FUNCTIONS
952         ///////////////////////////////////////////////////////////////////////////
953
954
955         ///////////////////////////////////////////////////////////////////////////
956         ////    STANDARD SUGARBEAN OVERRIDES
957         /**
958          * function overrides the one in SugarBean.php
959          */
960         function create_export_query($order_by, $where, $show_deleted = 0) {
961                 return $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted = 0);
962         }
963
964         /**
965          * function overrides the one in SugarBean.php
966          */
967
968         /**
969          * function overrides the one in SugarBean.php
970          */
971         function fill_in_additional_list_fields() {
972                 $this->fill_in_additional_detail_fields();
973         }
974
975         /**
976          * function overrides the one in SugarBean.php
977          */
978         function fill_in_additional_detail_fields() {
979     }
980
981         /**
982          * function overrides the one in SugarBean.php
983          */
984         function get_list_view_data(){
985                 global $mod_strings;
986                 $temp_array = $this->get_list_view_array();
987         $temp_array["ENCODED_NAME"]=$this->name;
988         $this->parseInterval();
989         $this->setIntervalHumanReadable();
990         $temp_array['JOB_INTERVAL'] = $this->intervalHumanReadable;
991         if($this->date_time_end == '2020-12-31 23:59' || $this->date_time_end == '') {
992                 $temp_array['DATE_TIME_END'] = $mod_strings['LBL_PERENNIAL'];
993         }
994         $this->created_by_name = get_assigned_user_name($this->created_by);
995                 $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
996         return $temp_array;
997
998         }
999
1000         /**
1001          * returns the bean name - overrides SugarBean's
1002          */
1003         function get_summary_text() {
1004                 return $this->name;
1005         }
1006         ////    END STANDARD SUGARBEAN OVERRIDES
1007         ///////////////////////////////////////////////////////////////////////////
1008 } // end class definition
1009 ?>