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