]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/Schedulers/Scheduler.php
Release 6.2.0
[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)->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                 if($focus->catch_up == 1) {
585                         if($focus->last_run == null) {
586                                 // always "catch-up"
587                                 $validJobTime[] = $timedate->nowDb();
588                         } else {
589                                 // determine what the interval in min/hours is
590                                 // see if last_run is in it
591                                 // if not, add NOW
592                 $now = $timedate->nowDb();
593                 if(!empty($validJobTime) && ($focus->last_run < $validJobTime[0]) && ($now > $validJobTime[0])) {
594                                 // cn: empty() bug 5914;
595                                 //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
596                                 //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.
597                                         $validJobTime[] = $now;
598                                 }
599                         }
600                 }
601                 return $validJobTime;
602         }
603
604         function handleIntervalType($type, $value, $mins, $hours) {
605                 global $mod_strings;
606                 /* [0]:min [1]:hour [2]:day of month [3]:month [4]:day of week */
607                 $days = array ( 0 => $mod_strings['LBL_MON'],
608                                                 1 => $mod_strings['LBL_TUE'],
609                                                 2 => $mod_strings['LBL_WED'],
610                                                 3 => $mod_strings['LBL_THU'],
611                                                 4 => $mod_strings['LBL_FRI'],
612                                                 5 => $mod_strings['LBL_SAT'],
613                                                 6 => $mod_strings['LBL_SUN'],
614                                                 '*' => $mod_strings['LBL_ALL']);
615                 switch($type) {
616                         case 0: // minutes
617                                 if($value == '0') {
618                                         //return;
619                                         return trim($mod_strings['LBL_ON_THE']).$mod_strings['LBL_HOUR_SING'];
620                                 } elseif(!preg_match('/[^0-9]/', $hours) && !preg_match('/[^0-9]/', $value)) {
621                                         return;
622
623                                 } elseif(preg_match('/\*\//', $value)) {
624                                         $value = str_replace('*/','',$value);
625                                         return $value.$mod_strings['LBL_MINUTES'];
626                                 } elseif(!preg_match('[^0-9]', $value)) {
627                                         return $mod_strings['LBL_ON_THE'].$value.$mod_strings['LBL_MIN_MARK'];
628                                 } else {
629                                         return $value;
630                                 }
631                         case 1: // hours
632                                 global $current_user;
633                                 if(preg_match('/\*\//', $value)) { // every [SOME INTERVAL] hours
634                                         $value = str_replace('*/','',$value);
635                                         return $value.$mod_strings['LBL_HOUR'];
636                                 } elseif(preg_match('/[^0-9]/', $mins)) { // got a range, or multiple of mins, so we return an 'Hours' label
637                                         return $value;
638                                 } else {        // got a "minutes" setting, so it will be at some o'clock.
639                                         $datef = $current_user->getUserDateTimePreferences();
640                                         return date($datef['time'], strtotime($value.':'.str_pad($mins, 2, '0', STR_PAD_LEFT)));
641                                 }
642                         case 2: // day of month
643                                 if(preg_match('/\*/', $value)) {
644                                         return $value;
645                                 } else {
646                                         return date('jS', strtotime('December '.$value));
647                                 }
648
649                         case 3: // months
650                                 return date('F', strtotime('2005-'.$value.'-01'));
651                         case 4: // days of week
652                                 return $days[$value];
653                         default:
654                                 return 'bad'; // no condition to touch this branch
655                 }
656         }
657
658         function setIntervalHumanReadable() {
659                 global $current_user;
660                 global $mod_strings;
661
662                 /* [0]:min [1]:hour [2]:day of month [3]:month [4]:day of week */
663                 $ints = $this->intervalParsed;
664                 $intVal = array('-', ',');
665                 $intSub = array($mod_strings['LBL_RANGE'], $mod_strings['LBL_AND']);
666                 $intInt = array(0 => $mod_strings['LBL_MINS'], 1 => $mod_strings['LBL_HOUR']);
667                 $tempInt = '';
668                 $iteration = '';
669
670                 foreach($ints['raw'] as $key => $interval) {
671                         if($tempInt != $iteration) {
672                                 $tempInt .= '; ';
673                         }
674                         $iteration = $tempInt;
675
676                         if($interval != '*' && $interval != '*/1') {
677                                 if(false !== strpos($interval, ',')) {
678                                         $exIndiv = explode(',', $interval);
679                                         foreach($exIndiv as $val) {
680                                                 if(false !== strpos($val, '-')) {
681                                                         $exRange = explode('-', $val);
682                                                         foreach($exRange as $valRange) {
683                                                                 if($tempInt != '') {
684                                                                         $tempInt .= $mod_strings['LBL_AND'];
685                                                                 }
686                                                                 $tempInt .= $this->handleIntervalType($key, $valRange, $ints['raw'][0], $ints['raw'][1]);
687                                                         }
688                                                 } elseif($tempInt != $iteration) {
689                                                         $tempInt .= $mod_strings['LBL_AND'];
690                                                 }
691                                                 $tempInt .= $this->handleIntervalType($key, $val, $ints['raw'][0], $ints['raw'][1]);
692                                         }
693                                 } elseif(false !== strpos($interval, '-')) {
694                                         $exRange = explode('-', $interval);
695                                         $tempInt .= $mod_strings['LBL_FROM'];
696                                         $check = $tempInt;
697
698                                         foreach($exRange as $val) {
699                                                 if($tempInt == $check) {
700                                                         $tempInt .= $this->handleIntervalType($key, $val, $ints['raw'][0], $ints['raw'][1]);
701                                                         $tempInt .= $mod_strings['LBL_RANGE'];
702
703                                                 } else {
704                                                         $tempInt .= $this->handleIntervalType($key, $val, $ints['raw'][0], $ints['raw'][1]);
705                                                 }
706                                         }
707
708                                 } elseif(false !== strpos($interval, '*/')) {
709                                         $tempInt .= $mod_strings['LBL_EVERY'];
710                                         $tempInt .= $this->handleIntervalType($key, $interval, $ints['raw'][0], $ints['raw'][1]);
711                                 } else {
712                                         $tempInt .= $this->handleIntervalType($key, $interval, $ints['raw'][0], $ints['raw'][1]);
713                                 }
714                         }
715                 } // end foreach()
716
717                 if($tempInt == '') {
718                         $this->intervalHumanReadable = $mod_strings['LBL_OFTEN'];
719                 } else {
720                         $tempInt = trim($tempInt);
721                         if(';' == substr($tempInt, (strlen($tempInt)-1), strlen($tempInt))) {
722                                 $tempInt = substr($tempInt, 0, (strlen($tempInt)-1));
723                         }
724                         $this->intervalHumanReadable = $tempInt;
725                 }
726         }
727
728
729         /* take an integer and return its suffix */
730         function setStandardArraysAttributes() {
731                 global $mod_strings;
732                 global $app_list_strings; // using from month _dom list
733
734                 $suffArr = array('','st','nd','rd');
735                 for($i=1; $i<32; $i++) {
736                         if($i > 3 && $i < 21) {
737                                 $this->suffixArray[$i] = $i."th";
738                         } elseif (substr($i,-1,1) < 4 && substr($i,-1,1) > 0) {
739                                 $this->suffixArray[$i] = $i.$suffArr[substr($i,-1,1)];
740                         } else {
741                                 $this->suffixArray[$i] = $i."th";
742                         }
743                         $this->datesArray[$i] = $i;
744                 }
745
746                 $this->dayInt = array('*',1,2,3,4,5,6,7);
747                 $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']);
748                 $this->monthsInt = array(0,1,2,3,4,5,6,7,8,9,10,11,12);
749                 $this->monthsLabel = $app_list_strings['dom_cal_month_long'];
750                 $this->metricsVar = array("*", "/", "-", ",");
751                 $this->metricsVal = array(' every ','',' thru ',' and ');
752         }
753
754         /**
755          *  takes the serialized interval string and renders it into an array
756          */
757         function parseInterval() {
758                 global $metricsVar;
759                 $ws = array(' ', '\r','\t');
760                 $blanks = array('','','');
761
762                 $intv = $this->job_interval;
763                 $rawValues = explode('::', $intv);
764                 $rawProcessed = str_replace($ws,$blanks,$rawValues); // strip all whitespace
765
766                 $hours = $rawValues[1].':::'.$rawValues[0];
767                 $months = $rawValues[3].':::'.$rawValues[2];
768
769                 $intA = array ( 'raw' => $rawProcessed,
770                                                 'hours' => $hours,
771                                                 'months' => $months,
772                                                 );
773
774                 $this->intervalParsed = $intA;
775         }
776
777         /**
778          * soft-deletes all job logs older than 24 hours
779          */
780         function cleanJobLog() 
781         {
782                 $GLOBALS['log']->info('DELETE FROM schedulers_times WHERE date_entered < '.db_convert("'" . TimeDate::getInstance()->getNow()->get("-1 day")->asDb() . "'", 'datetime'));
783                 $this->db->query('DELETE FROM schedulers_times WHERE date_entered < '.db_convert("'" . TimeDate::getInstance()->getNow()->get("-1 day")->asDb() . "'", 'datetime'));
784         }
785
786         /**
787          * checks for cURL libraries
788          */
789         function checkCurl() {
790                 global $mod_strings;
791
792                 if(!function_exists('curl_init')) {
793                         echo '
794                         <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
795                                 <tr height="20">
796                                         <th width="25%" colspan="2"><slot>
797                                                 '.$mod_strings['LBL_WARN_CURL_TITLE'].'
798                                         </slot></td>
799                                 </tr>
800                                 <tr class="oddListRowS1" >
801                                         <td scope="row" valign=TOP width="20%"><slot>
802                                                 '.$mod_strings['LBL_WARN_CURL'].'
803                                         <td scope="row" valign=TOP width="80%"><slot>
804                                                 <span class=error>'.$mod_strings['LBL_WARN_NO_CURL'].'</span>
805                                         </slot></td>
806                                 </tr>
807                         </table>
808                         <br>';
809                 }
810         }
811
812         function displayCronInstructions() {
813                 global $mod_strings;
814                 global $sugar_config;
815                 $error = '';
816                 if (!isset($_SERVER['Path'])) {
817             $_SERVER['Path'] = getenv('Path');
818         }
819         if(is_windows()) {
820                         if(isset($_SERVER['Path']) && !empty($_SERVER['Path'])) { // IIS IUSR_xxx may not have access to Path or it is not set
821                                 if(!strpos($_SERVER['Path'], 'php')) {
822                                         $error = '<em>'.$mod_strings['LBL_NO_PHP_CLI'].'</em>';
823                                 }
824                         }
825                 } else {
826                         if(isset($_SERVER['Path']) && !empty($_SERVER['Path'])) { // some Linux servers do not make this available
827                                 if(!strpos($_SERVER['PATH'], 'php')) {
828                                         $error = '<em>'.$mod_strings['LBL_NO_PHP_CLI'].'</em>';
829                                 }
830                         }
831                 }
832
833
834
835                 if(is_windows()) {
836                         echo '<br>';
837                         echo '
838                                 <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
839                                 <tr height="20">
840                                         <th><slot>
841                                                 '.$mod_strings['LBL_CRON_INSTRUCTIONS_WINDOWS'].'
842                                         </slot></th>
843                                 </tr>
844                                 <tr class="evenListRowS1">
845                                         <td scope="row" valign="top" width="70%"><slot>
846                                                 '.$mod_strings['LBL_CRON_WINDOWS_DESC'].'<br>
847                                                 <b>cd '.realpath('./').'<br>
848                                                 php.exe -f cron.php</b>
849                                         </slot></td>
850                                 </tr>
851                         </table>';
852                 } else {
853                         echo '<br>';
854                         echo '
855                                 <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
856                                 <tr height="20">
857                                         <th><slot>
858                                                 '.$mod_strings['LBL_CRON_INSTRUCTIONS_LINUX'].'
859                                         </slot></th>
860                                 </tr>
861                                 <tr>
862                                         <td scope="row" valign=TOP class="oddListRowS1" bgcolor="#fdfdfd" width="70%"><slot>
863                                                 '.$mod_strings['LBL_CRON_LINUX_DESC'].'<br>
864                                                 <b>*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;
865                                                 cd '.realpath('./').'; php -f cron.php > /dev/null 2>&1</b>
866                                                 <br>'.$error.'
867                                         </slot></td>
868                                 </tr>
869                         </table>';
870                 }
871         }
872
873         /**
874          * Archives schedulers of the same functionality, then instantiates new
875          * ones.
876          */
877         function rebuildDefaultSchedulers() {
878                 global $mod_strings;
879                 // truncate scheduler-related tables
880                 $this->db->query('DELETE FROM schedulers');
881                 $this->db->query('DELETE FROM schedulers_times');
882
883
884         $sched3 = new Scheduler();
885         $sched3->name               = $mod_strings['LBL_OOTB_TRACKER'];
886         $sched3->job                = 'function::trimTracker';
887         $sched3->date_time_start    = create_date(2005,1,1) . ' ' . create_time(0,0,1);
888         $sched3->date_time_end      = create_date(2020,12,31) . ' ' . create_time(23,59,59);
889         $sched3->job_interval       = '0::2::1::*::*';
890         $sched3->status             = 'Active';
891         $sched3->created_by         = '1';
892         $sched3->modified_user_id   = '1';
893         $sched3->catch_up           = '1';
894         $sched3->save();
895                 $sched4 = new Scheduler();
896                 $sched4->name                           = $mod_strings['LBL_OOTB_IE'];
897                 $sched4->job                            = 'function::pollMonitoredInboxes';
898                 $sched4->date_time_start        = create_date(2005,1,1) . ' ' . create_time(0,0,1);
899                 $sched4->date_time_end          = create_date(2020,12,31) . ' ' . create_time(23,59,59);
900                 $sched4->job_interval           = '*::*::*::*::*';
901                 $sched4->status                         = 'Active';
902                 $sched4->created_by                     = '1';
903                 $sched4->modified_user_id       = '1';
904                 $sched4->catch_up                       = '0';
905                 $sched4->save();
906
907                 $sched5 = new Scheduler();
908                 $sched5->name                           = $mod_strings['LBL_OOTB_BOUNCE'];
909                 $sched5->job                            = 'function::pollMonitoredInboxesForBouncedCampaignEmails';
910                 $sched5->date_time_start        = create_date(2005,1,1) . ' ' . create_time(0,0,1);
911                 $sched5->date_time_end          = create_date(2020,12,31) . ' ' . create_time(23,59,59);
912                 $sched5->job_interval           = '0::2-6::*::*::*';
913                 $sched5->status                         = 'Active';
914                 $sched5->created_by                     = '1';
915                 $sched5->modified_user_id       = '1';
916                 $sched5->catch_up                       = '1';
917                 $sched5->save();
918
919                 $sched6 = new Scheduler();
920                 $sched6->name                           = $mod_strings['LBL_OOTB_CAMPAIGN'];
921                 $sched6->job                            = 'function::runMassEmailCampaign';
922                 $sched6->date_time_start        = create_date(2005,1,1) . ' ' . create_time(0,0,1);
923                 $sched6->date_time_end          = create_date(2020,12,31) . ' ' . create_time(23,59,59);
924                 $sched6->job_interval           = '0::2-6::*::*::*';
925                 $sched6->status                         = 'Active';
926                 $sched6->created_by                     = '1';
927                 $sched6->modified_user_id       = '1';
928                 $sched6->catch_up                       = '1';
929                 $sched6->save();
930
931
932         $sched7 = new Scheduler();
933         $sched7->name               = $mod_strings['LBL_OOTB_PRUNE'];
934         $sched7->job                = 'function::pruneDatabase';
935         $sched7->date_time_start    = create_date(2005,1,1) . ' ' . create_time(0,0,1);
936         $sched7->date_time_end      = create_date(2020,12,31) . ' ' . create_time(23,59,59);
937         $sched7->job_interval       = '0::4::1::*::*';
938         $sched7->status             = 'Inactive';
939         $sched7->created_by         = '1';
940         $sched7->modified_user_id   = '1';
941         $sched7->catch_up           = '0';
942         $sched7->save();
943
944
945
946
947
948         }
949
950         ////    END SCHEDULER HELPER FUNCTIONS
951         ///////////////////////////////////////////////////////////////////////////
952
953
954         ///////////////////////////////////////////////////////////////////////////
955         ////    STANDARD SUGARBEAN OVERRIDES
956         /**
957          * function overrides the one in SugarBean.php
958          */
959         function create_export_query($order_by, $where, $show_deleted = 0) {
960                 return $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted = 0);
961         }
962
963         /**
964          * function overrides the one in SugarBean.php
965          */
966
967         /**
968          * function overrides the one in SugarBean.php
969          */
970         function fill_in_additional_list_fields() {
971                 $this->fill_in_additional_detail_fields();
972         }
973
974         /**
975          * function overrides the one in SugarBean.php
976          */
977         function fill_in_additional_detail_fields() {
978     }
979
980         /**
981          * function overrides the one in SugarBean.php
982          */
983         function get_list_view_data(){
984                 global $mod_strings;
985                 $temp_array = $this->get_list_view_array();
986         $temp_array["ENCODED_NAME"]=$this->name;
987         $this->parseInterval();
988         $this->setIntervalHumanReadable();
989         $temp_array['JOB_INTERVAL'] = $this->intervalHumanReadable;
990         if($this->date_time_end == '2020-12-31 23:59' || $this->date_time_end == '') {
991                 $temp_array['DATE_TIME_END'] = $mod_strings['LBL_PERENNIAL'];
992         }
993         $this->created_by_name = get_assigned_user_name($this->created_by);
994                 $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
995         return $temp_array;
996
997         }
998
999         /**
1000          * returns the bean name - overrides SugarBean's
1001          */
1002         function get_summary_text() {
1003                 return $this->name;
1004         }
1005         ////    END STANDARD SUGARBEAN OVERRIDES
1006         ///////////////////////////////////////////////////////////////////////////
1007 } // end class definition
1008 ?>