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