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