]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/Schedulers/Scheduler.php
Release 6.1.4
[Github/sugarcrm.git] / modules / Schedulers / Scheduler.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38 /*********************************************************************************
39
40  * Description:
41  ********************************************************************************/
42
43
44
45
46
47
48 class Scheduler extends SugarBean {
49         // table columns
50         var $id;
51         var $deleted;
52         var $date_entered;
53         var $date_modified;
54         var $modified_user_id;
55         var $created_by;
56         var $created_by_name;
57         var $modified_by_name;
58         var $name;
59         var $job;
60         var $date_time_start;
61         var $date_time_end;
62         var $job_interval;
63         var $time_from;
64         var $time_to;
65         var $last_run;
66         var $status;
67         var $catch_up;
68         // object attributes
69         var $user;
70         var $intervalParsed;
71         var $intervalHumanReadable;
72         var $metricsVar;
73         var $metricsVal;
74         var $dayInt;
75         var $dayLabel;
76         var $monthsInt;
77         var $monthsLabel;
78         var $suffixArray;
79         var $datesArray;
80         var $scheduledJobs;
81         var $timeOutMins = 60;
82         // standard SugarBean attrs
83         var $table_name                         = "schedulers";
84         var $object_name                        = "schedulers";
85         var $module_dir                         = "Schedulers";
86         var $new_schema                         = true;
87         var $process_save_dates         = true;
88         var $order_by;
89
90
91         function Scheduler($init=true) {
92                 parent::SugarBean();
93
94                 if($init) {
95                         
96                         $user = new User();
97                         $user->retrieve('1'); // Scheduler jobs run as Admin
98                         $this->user = $user;
99                 }
100         }
101
102
103         ///////////////////////////////////////////////////////////////////////////
104         ////    SCHEDULER HELPER FUNCTIONS
105         /**
106          * executes Scheduled job
107          */
108         function fire() {
109                 if(empty($this->job)) { // only execute when valid
110                         $GLOBALS['log']->fatal('Scheduler tried to fire an empty job!!');
111                         return false;
112                 }
113
114                 $exJob = explode('::', $this->job);
115                 if(is_array($exJob)) {
116                         // instantiate a new SchedulersJob object and prep it
117                         
118                         
119                         $trackerManager = TrackerManager::getInstance();
120                         $trackerManager->pause();                       
121                         $job                            = new SchedulersJob();
122                         $job->scheduler_id      = $this->id;
123                         $job->scheduler         =& $this;
124                         $job->execute_time      = $job->handleDateFormat('now');
125                         $jobId = $job->save();
126                         $trackerManager->unPause();                     
127                         $job->retrieve($jobId);
128
129                         if($exJob[0] == 'function') {
130                                 $GLOBALS['log']->debug('----->Scheduler found a job of type FUNCTION');
131                                 require_once('modules/Schedulers/_AddJobsHere.php');
132
133                                 $job->setJobFlag(1);
134
135                                 $func = $exJob[1];
136                                 $GLOBALS['log']->debug('----->SchedulersJob firing '.$func);
137
138                                 $res = call_user_func($func);
139                                 if($res) {
140                                         $job->setJobFlag(2);
141                                         $job->finishJob();
142                                         return true;
143                                 } else {
144                                         $job->setJobFlag(3);
145                                         return false;
146                                 }
147                         } elseif($exJob[0] == 'url') {
148                                 if(function_exists('curl_init')) {
149                                         $GLOBALS['log']->debug('----->SchedulersJob found a job of type URL');
150                                         $job->setJobFlag(1);
151
152                                         $GLOBALS['log']->debug('----->SchedulersJob firing URL job: '.$exJob[1]);
153                                         if($job->fireUrl($exJob[1])) {
154                                                 $job->setJobFlag(2);
155                                                 $job->finishJob();
156                                                 return true;
157                                         } else {
158                                                 $job->setJobFlag(3);
159                                                 return false;
160                                         }
161                                 } else {
162                                         $job->setJobFlag(4);
163                                         return false;
164                                 }
165                         }
166                 }
167                 return false;
168         }
169
170         /**
171          * flushes dead or hung jobs
172          */
173         function flushDeadJobs() {
174                 $GLOBALS['log']->debug('-----> Scheduler flushing dead jobs');
175
176                 $lowerLimit = mktime(0, 0, 0, 1, 1, 2005); // jan 01, 2005, GMT-0
177                 $now = strtotime(gmdate('Y-m-d H:i:s', strtotime('now'))); // this garbage to make sure we're getting comprable data to the DB output
178
179                 $q = "  SELECT s.id, s.name FROM schedulers s WHERE s.deleted=0 AND s.status = 'In Progress'";
180                 $r = $this->db->query($q);
181
182                 if($r != null) {
183                         while($a = $this->db->fetchByAssoc($r)) {
184                                 $q2 = " SELECT st.id, st.execute_time FROM schedulers_times st
185                                                 WHERE st.deleted=0
186                                                 AND st.scheduler_id = '{$a['id']}'
187                                                 ORDER BY st.execute_time DESC";
188                                 $r2 = $this->db->query($q2);
189                                 if($r2 != null) {
190                                         $a2 = $this->db->fetchByAssoc($r2); // we only care about the newest
191                                         if($a2 != null) {
192                                                 $GLOBALS['log']->debug("-----> Scheduler found [ {$a['name']} ] 'In Progress' with most recent Execute Time at [ {$a2['execute_time']} GMT-0 ]");
193
194                                                 $execTime = strtotime($a2['execute_time']);
195                                                 if($execTime > $lowerLimit) {
196                                                         if(($now - $execTime) >= (60 * $this->timeOutMins)) {
197                                                                 $GLOBALS['log']->info("-----> Scheduler found a dead Job.  Flushing status and reseting Job");
198                                                                 $q3 = "UPDATE schedulers SET status = 'Active' WHERE id = '{$a['id']}'";
199                                                                 $this->db->query($q3);
200
201                                                                 $GLOBALS['log']->info("-----> Scheduler setting Job Instance status to 'failed'");
202                                                                 $q4 = "UPDATE schedulers_times SET status = 'failed' WHERE id = '{$a2['id']}';";
203                                                                 $this->db->query($q4);
204                                                         } else {
205                                                                 $GLOBALS['log']->debug("-----> Scheduler will wait for job to complete - not past threshold of [ ".($this->timeOutMins * 60)."secs ] - timeDiff is ".($now - $execTime)." secs");
206                                                         }
207                                                 } else {
208                                                         $GLOBALS['log']->fatal("-----> Scheduler got a bad execute time:        [ {$a2['execute_time']} GMT-0 ]");
209                                                 }
210
211                                         }
212                                 }
213                         }
214                 } // if
215         }
216
217         /**
218          * calculates if a job is qualified to run
219          */
220         function fireQualified() {
221                 if(empty($this->id)) { // execute only if we have an instance
222                         $GLOBALS['log']->fatal('Scheduler called fireQualified() in a non-instance');
223                         return false;
224                 }
225
226                 $now = gmdate('Y-m-d H:i', strtotime('now'));
227                 $validTimes = $this->deriveDBDateTimes($this);
228                 //_pp('now: '.$now); _pp($validTimes);
229
230                 if(is_array($validTimes) && in_array($now, $validTimes)) {
231                         $GLOBALS['log']->debug('----->Scheduler found valid job ('.$this->name.') for time GMT('.$now.')');
232                         return true;
233                 } else {
234                         $GLOBALS['log']->debug('----->Scheduler did NOT find valid job ('.$this->name.') for time GMT('.$now.')');
235                         return false;
236                 }
237         }
238
239
240
241
242
243
244
245
246         /**
247          * Checks if any jobs qualify to run at this moment
248          */
249         function checkPendingJobs2() {
250                 $this->cleanJobLog();
251                 $allSchedulers = $this->get_full_list('', 'schedulers.status=\'Active\'');
252
253
254                 if(!empty($allSchedulers)) {
255
256                         // cURL inits
257                         $ch = curl_init();
258 ////                    curl_setopt($ch, CURLOPT_FAILONERROR, true); // silent failure (code >300);
259 ////                    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // do not follow location(); inits - we always use the current
260 ////                    curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
261 ////                    curl_setopt($ch, CURLOPT_DNS_USE_GLOBAL_CACHE, false);  // not thread-safe
262 //                      curl_setopt($ch, CURLOPT_TIMEOUT, 5); // never times out - bad idea?
263 //                      curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); // 5 secs for connect timeout
264 //                      curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);  // open brand new conn
265 ////                    curl_setopt($ch, CURLOPT_NOPROGRESS, true); // do not have progress bar
266 ////                    curl_setopt($ch, CURLOPT_PORT, $_SERVER['SERVER_PORT']); // set port as reported by Server
267 ////                    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // most customers will not have Certificate Authority account
268 ////                    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // most customers will not have Certificate Authority account
269 //                      if(constant('PHP_VERSION') > '5.0.0') {
270 ////                            curl_setopt($ch, CURLOPT_NOSIGNAL, true); // ignore any cURL signals to PHP (for multi-threading)
271 //                      }
272 //                      /* play with these options */
273 ////                    curl_setopt($ch, CURLOPT_HEADER, false); // do not return header info with result
274 ////                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // return into a variable to continue program execution
275 //
276 //
277 //                      foreach($allSchedulers as $focus) {
278 //                              if($focus->fireQualified()) {
279 //                                      $GLOBALS['log']->debug('---------------------- GOT A JOB FOR CURL -----------');
280 //                                      curl_setopt($ch, CURLOPT_URL, 'http://127.0.0.1/headdev/cronJob.php?id='.$focus->id); // set url
281 //                              }
282 //                      }
283                         curl_setopt($ch, CURLOPT_URL, 'http://127.0.0.1/headdev/cronJob.php?id=82a9421a-9c60-111b-7212-4412394279e4'); // set url
284                         curl_setopt($ch, CURLOPT_URL, 'http://127.0.0.1/headdev/cronJob.php?id=82a9421a-9c60-111b-7212-4412394279e4');
285
286                         $result = curl_exec($ch);
287                         $cInfo = curl_getinfo($ch);/* url,content_type,header_size,request_size,filetime,http_code,ssl_verify_result,total_time,namelookup_time,connect_time,pretransfer_time,size_upload,size_download,speed_download,speed_upload,download_content_length,upload_content_length,starttransfer_time,redirect_time */
288                         curl_close($ch);
289
290                         if($cInfo['http_code'] < 400) {
291                                 $GLOBALS['log']->debug('----->Firing was successful: ('.$focus->id.') at');
292                                 $GLOBALS['log']->debug('----->WTIH RESULT: '.strip_tags($result).' AND '.strip_tags(print_r($cInfo)));
293                                 return true;
294                         } else {
295                                 $GLOBALS['log']->fatal('Job errored: ('.$focus->id.')');
296                                 return false;
297                         }
298
299
300
301                 } else {
302                         $GLOBALS['log']->debug('----->No Schedulers found');
303                 }
304         }
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336         /**
337          * Checks if any jobs qualify to run at this moment
338          */
339         function checkPendingJobs() {
340                 $this->cleanJobLog();
341                 $allSchedulers = $this->get_full_list('', 'schedulers.status=\'Active\'');
342
343                 $GLOBALS['log']->info('-----> Scheduler found [ '.count($allSchedulers).' ] ACTIVE jobs');
344
345                 if(!empty($allSchedulers)) {
346                         foreach($allSchedulers as $focus) {
347                                 if($focus->fireQualified()) {
348                                         if($focus->fire()) {
349                                                 $GLOBALS['log']->debug('----->Scheduler Job completed successfully');
350                                         } else {
351                                                 $GLOBALS['log']->fatal('----->Scheduler Job FAILED');
352                                         }
353                                 }
354                         }
355                 } else {
356                         $GLOBALS['log']->debug('----->No Schedulers found');
357                 }
358         }
359
360         /**
361          * This function takes a Scheduler object and uses its job_interval
362          * attribute to derive DB-standard datetime strings, as many as are
363          * qualified by its ranges.  The times are from the time of calling the
364          * script.
365          *
366          * @param       $focus          Scheduler object
367          * @return      $dateTimes      array loaded with DB datetime strings derived from
368          *                                              the      job_interval attribute
369          * @return      false           If we the Scheduler is not in scope, return false.
370          */
371         function deriveDBDateTimes($focus) {
372                 $GLOBALS['log']->debug('----->Schedulers->deriveDBDateTimes() got an object of type: '.$focus->object_name);
373                 /* [min][hr][dates][mon][days] */
374                 $dateTimes = array();
375                 $ints   = explode('::', str_replace(' ','',$focus->job_interval));
376                 $days   = $ints[4];
377                 $mons   = $ints[3];
378                 $dates  = $ints[2];
379                 $hrs    = $ints[1];
380                 $mins   = $ints[0];
381                 $today  = getdate(gmmktime());
382
383                 // derive day part
384                 if($days == '*') {
385                         $GLOBALS['log']->debug('----->got * day');
386
387                 } elseif(strstr($days, '*/')) {
388                         // the "*/x" format is nonsensical for this field
389                         // do basically nothing.
390                         $theDay = str_replace('*/','',$days);
391                         $dayName[] = $theDay;
392                 } elseif($days != '*') { // got particular day(s)
393                         if(strstr($days, ',')) {
394                                 $exDays = explode(',',$days);
395                                 foreach($exDays as $k1 => $dayGroup) {
396                                         if(strstr($dayGroup,'-')) {
397                                                 $exDayGroup = explode('-', $dayGroup); // build up range and iterate through
398                                                 for($i=$exDayGroup[0];$i<=$exDayGroup[1];$i++) {
399                                                         $dayName[] = $i;
400                                                 }
401                                         } else { // individuals
402                                                 $dayName[] = $dayGroup;
403                                         }
404                                 }
405                         } elseif(strstr($days, '-')) {
406                                 $exDayGroup = explode('-', $days); // build up range and iterate through
407                                 for($i=$exDayGroup[0];$i<=$exDayGroup[1];$i++) {
408                                         $dayName[] = $i;
409                                 }
410                         } else {
411                                 $dayName[] = $days;
412                         }
413
414                         // check the day to be in scope:
415                         if(!in_array(($today['wday']+6)%7, $dayName)) {//$dayName starts from Monday, while $today['wday'] starts from Sunday
416                                 return false;
417                         }
418                 } else {
419                         return false;
420                 }
421
422
423                 // derive months part
424                 if($mons == '*') {
425                         $GLOBALS['log']->debug('----->got * months');
426                 } elseif(strstr($mons, '*/')) {
427                         $mult = str_replace('*/','',$mons);
428                         $startMon = date('m', strtotime($focus->date_time_start));
429                         $startFrom = ($startMon % $mult);
430
431                         for($i=$startFrom;$i<=12;$i+$mult) {
432                                 $compMons[] = $i+$mult;
433                                 $i += $mult;
434                         }
435                         // this month is not in one of the multiplier months
436                         if(!in_array($today['mon'],$compMons)) {
437                                 return false;
438                         }
439                 } elseif($mons != '*') {
440                         if(strstr($mons,',')) { // we have particular (groups) of months
441                                 $exMons = explode(',',$mons);
442                                 foreach($exMons as $k1 => $monGroup) {
443                                         if(strstr($monGroup, '-')) { // we have a range of months
444                                                 $exMonGroup = explode('-',$monGroup);
445                                                 for($i=$exMonGroup[0];$i<=$exMonGroup[1];$i++) {
446                                                         $monName[] = $i;
447                                                 }
448                                         } else {
449                                                 $monName[] = $monGroup;
450                                         }
451                                 }
452                         } elseif(strstr($mons, '-')) {
453                                 $exMonGroup = explode('-', $mons);
454                                 for($i=$exMonGroup[0];$i<=$exMonGroup[1];$i++) {
455                                         $monName[] = $i;
456                                 }
457                         } else { // one particular month
458                                 $monName[] = $mons;
459                         }
460
461                         // check that particular months are in scope
462                         if(!in_array($today['mon'], $monName)) {
463                                 return false;
464                         }
465                 }
466
467                 // derive dates part
468                 if($dates == '*') {
469                         $GLOBALS['log']->debug('----->got * dates');
470                 } elseif(strstr($dates, '*/')) {
471                         $mult = str_replace('*/','',$dates);
472                         $startDate = date('d', strtotime($focus->date_time_start));
473                         $startFrom = ($startDate % $mult);
474
475                         for($i=$startFrom; $i<=31; $i+$mult) {
476                                 $dateName[] = str_pad(($i+$mult),2,'0',STR_PAD_LEFT);
477                                 $i += $mult;
478                         }
479
480                         if(!in_array($today['mday'], $dateName)) {
481                                 return false;
482                         }
483                 } elseif($dates != '*') {
484                         if(strstr($dates, ',')) {
485                                 $exDates = explode(',', $dates);
486                                 foreach($exDates as $k1 => $dateGroup) {
487                                         if(strstr($dateGroup, '-')) {
488                                                 $exDateGroup = explode('-', $dateGroup);
489                                                 for($i=$exDateGroup[0];$i<=$exDateGroup[1];$i++) {
490                                                         $dateName[] = $i;
491                                                 }
492                                         } else {
493                                                 $dateName[] = $dateGroup;
494                                         }
495                                 }
496                         } elseif(strstr($dates, '-')) {
497                                 $exDateGroup = explode('-', $dates);
498                                 for($i=$exDateGroup[0];$i<=$exDateGroup[1];$i++) {
499                                         $dateName[] = $i;
500                                 }
501                         } else {
502                                 $dateName[] = $dates;
503                         }
504
505                         // check that dates are in scope
506                         if(!in_array($today['mday'], $dateName)) {
507                                 return false;
508                         }
509                 }
510
511                 // derive hours part
512                 //$currentHour = gmdate('G');
513                 //$currentHour = date('G', strtotime('00:00'));
514                 if($hrs == '*') {
515                         $GLOBALS['log']->debug('----->got * hours');
516                         for($i=0;$i<24; $i++) {
517                                 $hrName[]=$i;
518                         }
519                 } elseif(strstr($hrs, '*/')) {
520                         $mult = str_replace('*/','',$hrs);
521                         for($i=0; $i<24; $i) { // weird, i know
522                                 $hrName[]=$i;
523                                 $i += $mult;
524                         }
525                 } elseif($hrs != '*') {
526                         if(strstr($hrs, ',')) {
527                                 $exHrs = explode(',',$hrs);
528                                 foreach($exHrs as $k1 => $hrGroup) {
529                                         if(strstr($hrGroup, '-')) {
530                                                 $exHrGroup = explode('-', $hrGroup);
531                                                 for($i=$exHrGroup[0];$i<=$exHrGroup[1];$i++) {
532                                                         $hrName[] = $i;
533                                                 }
534                                         } else {
535                                                 $hrName[] = $hrGroup;
536                                         }
537                                 }
538                         } elseif(strstr($hrs, '-')) {
539                                 $exHrs = explode('-', $hrs);
540                                 for($i=$exHrs[0];$i<=$exHrs[1];$i++) {
541                                         $hrName[] = $i;
542                                 }
543                         } else {
544                                 $hrName[] = $hrs;
545                         }
546                 }
547                 //_pp($hrName);
548                 // derive minutes
549                 //$currentMin = date('i');
550                 $currentMin = date('i', strtotime($this->date_time_start));
551                 if(substr($currentMin, 0, 1) == '0') {
552                         $currentMin = substr($currentMin, 1, 1);
553                 }
554                 if($mins == '*') {
555                         $GLOBALS['log']->debug('----->got * mins');
556                         for($i=0; $i<60; $i++) {
557                                 if(($currentMin + $i) > 59) {
558                                         $minName[] = ($i + $currentMin - 60);
559                                 } else {
560                                         $minName[] = ($i+$currentMin);
561                                 }
562                         }
563                 } elseif(strstr($mins,'*/')) {
564                         $mult = str_replace('*/','',$mins);
565                         $startMin = date('i',strtotime($focus->date_time_start));
566                         $startFrom = ($startMin % $mult);
567                         for($i=$startFrom; $i<=59; $i) {
568                                 if(($currentMin + $i) > 59) {
569                                         $minName[] = ($i + $currentMin - 60);
570                                 } else {
571                                         $minName[] = ($i+$currentMin);
572                                 }
573                                 $i += $mult;
574                         }
575
576                 } elseif($mins != '*') {
577                         if(strstr($mins, ',')) {
578                                 $exMins = explode(',',$mins);
579                                 foreach($exMins as $k1 => $minGroup) {
580                                         if(strstr($minGroup, '-')) {
581                                                 $exMinGroup = explode('-', $minGroup);
582                                                 for($i=$exMinGroup[0]; $i<=$exMinGroup[1]; $i++) {
583                                                         $minName[] = $i;
584                                                 }
585                                         } else {
586                                                 $minName[] = $minGroup;
587                                         }
588                                 }
589                         } elseif(strstr($mins, '-')) {
590                                 $exMinGroup = explode('-', $mins);
591                                 for($i=$exMinGroup[0]; $i<=$exMinGroup[1]; $i++) {
592                                         $minName[] = $i;
593                                 }
594                         } else {
595                                 $minName[] = $mins;
596                         }
597                 }
598                 //_pp($minName);
599                 // prep some boundaries - these are not in GMT b/c gmt is a 24hour period, possibly bridging 2 local days
600                 if(empty($focus->time_from)  && empty($focus->time_to) ) {
601                         $timeFromTs = 0;
602                         $timeToTs = strtotime('+1 day');
603                 } else {
604                         $timeFromTs = strtotime($focus->time_from);     // these are now GMT (timestamps are all GMT)
605                         $timeToTs       = strtotime($focus->time_to);   // see above
606                         if($timeFromTs > $timeToTs) { // we've crossed into the next day
607                                 $timeToTs = strtotime('+1 day '. $focus->time_to);      // also in GMT
608                         }
609                 }
610                 $timeToTs++;
611
612                 if(empty($focus->last_run)) {
613                         $lastRunTs = 0;
614                 } else {
615                         $lastRunTs = strtotime($focus->last_run . ' UTC');
616                 }
617
618
619                 /**
620                  * initialize return array
621                  */
622                 $validJobTime = array();
623
624                 global $timedate;
625                 $timeStartTs = strtotime($focus->date_time_start . ' UTC');
626                 if(!empty($focus->date_time_end)) { // do the same for date_time_end if not empty
627                         $dateTimeEnd = $focus->date_time_end;
628                 } else {
629                         $dateTimeEnd = gmdate('Y-m-d H:i:s', strtotime('+1 day'));
630 //                      $dateTimeEnd = '2020-12-31 23:59:59'; // if empty, set it to something ridiculous
631                 }
632                 $timeEndTs = strtotime($dateTimeEnd.' UTC'); // GMT end timestamp if necessary
633                 $timeEndTs++;
634                 /*_pp('hours:'); _pp($hrName);_pp('mins:'); _pp($minName);*/
635                 $nowTs = mktime();
636
637 //              _pp('currentHour: '. $currentHour);
638 //              _pp('timeStartTs: '.date('r',$timeStartTs));
639 //              _pp('timeFromTs: '.date('r',$timeFromTs));
640 //              _pp('timeEndTs: '.date('r',$timeEndTs));
641 //              _pp('timeToTs: '.date('r',$timeToTs));
642 //              _pp('mktime: '.date('r',mktime()));
643 //              _pp('timeLastRun: '.date('r',$lastRunTs));
644 //
645 //              _pp('hours: ');
646 //              _pp($hrName);
647 //              _pp('mins: ');
648 //              _ppd($minName);
649                 $hourSeen = 0;
650                 foreach($hrName as $kHr=>$hr) {
651                         $hourSeen++;
652                         foreach($minName as $kMin=>$min) {
653                                 if($hourSeen == 25) {
654                                         $theDate = date('Y-m-d', strtotime('+1 day'));
655                                 } else {
656                                         $theDate = date('Y-m-d');
657                                 }
658
659                                 $tsGmt = strtotime($theDate.' '.str_pad($hr,2,'0',STR_PAD_LEFT).":".str_pad($min,2,'0',STR_PAD_LEFT).":00"); // this is LOCAL
660
661                                 if( $tsGmt >= $timeStartTs ) { // start is greater than the date specified by admin
662                                         if( $tsGmt >= $timeFromTs ) { // start is greater than the time_to spec'd by admin
663                         if($tsGmt > $lastRunTs) { // start from last run, last run should not be included
664                             if( $tsGmt <= $timeEndTs ) { // this is taken care of by the initial query - start is less than the date spec'd by admin
665                                 if( $tsGmt <= $timeToTs ) { // start is less than the time_to
666                                     $validJobTime[] = gmdate('Y-m-d H:i', $tsGmt);
667                                 } else {
668                                     //_pp('Job Time is NOT smaller that TimeTO: '.$tsGmt .'<='. $timeToTs);     
669                                 }
670                             } else {
671                                 //_pp('Job Time is NOT smaller that DateTimeEnd: '.date('Y-m-d H:i:s',$tsGmt) .'<='. $dateTimeEnd); //_pp( $tsGmt .'<='. $timeEndTs );
672                             }
673                         }
674                                         } else {
675                                                 //_pp('Job Time is NOT bigger that TimeFrom: '.$tsGmt .'>='. $timeFromTs);
676                                         }
677                                 } else {
678                                         //_pp('Job Time is NOT Bigger than DateTimeStart: '.date('Y-m-d H:i',$tsGmt) .'>='. $dateTimeStart);
679                                 }
680                         }
681                 }
682                 //_ppd($validJobTime);
683                 // need ascending order to compare oldest time to last_run
684                 sort($validJobTime);
685                 /**
686                  * If "Execute If Missed bit is set
687                  */
688                 if($focus->catch_up == 1) {
689                         if($focus->last_run == null) {
690                                 // always "catch-up"
691                                 $validJobTime[] = gmdate('Y-m-d H:i', strtotime('now'));
692                         } else {
693                                 // determine what the interval in min/hours is
694                                 // see if last_run is in it
695                                 // if not, add NOW
696                 $now = gmdate('Y-m-d H:i', strtotime('now'));
697                                 if(!empty($validJobTime) && ($focus->last_run < $validJobTime[0]) && ($now > $validJobTime[0])) {
698                                 // cn: empty() bug 5914; 
699                                 //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
700                                 //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.
701                                         $validJobTime[] = $now;
702                                 }
703                         }
704                 }
705                 return $validJobTime;
706         }
707
708         function handleIntervalType($type, $value, $mins, $hours) {
709                 global $mod_strings;
710                 /* [0]:min [1]:hour [2]:day of month [3]:month [4]:day of week */
711                 $days = array ( 0 => $mod_strings['LBL_MON'],
712                                                 1 => $mod_strings['LBL_TUE'],
713                                                 2 => $mod_strings['LBL_WED'],
714                                                 3 => $mod_strings['LBL_THU'],
715                                                 4 => $mod_strings['LBL_FRI'],
716                                                 5 => $mod_strings['LBL_SAT'],
717                                                 6 => $mod_strings['LBL_SUN'],
718                                                 '*' => $mod_strings['LBL_ALL']);
719                 switch($type) {
720                         case 0: // minutes
721                                 if($value == '0') {
722                                         //return;
723                                         return trim($mod_strings['LBL_ON_THE']).$mod_strings['LBL_HOUR_SING'];
724                                 } elseif(!preg_match('/[^0-9]/', $hours) && !preg_match('/[^0-9]/', $value)) {
725                                         return;
726
727                                 } elseif(preg_match('/\*\//', $value)) {
728                                         $value = str_replace('*/','',$value);
729                                         return $value.$mod_strings['LBL_MINUTES'];
730                                 } elseif(!preg_match('[^0-9]', $value)) {
731                                         return $mod_strings['LBL_ON_THE'].$value.$mod_strings['LBL_MIN_MARK'];
732                                 } else {
733                                         return $value;
734                                 }
735                         case 1: // hours
736                                 global $current_user;
737                                 if(preg_match('/\*\//', $value)) { // every [SOME INTERVAL] hours
738                                         $value = str_replace('*/','',$value);
739                                         return $value.$mod_strings['LBL_HOUR'];
740                                 } elseif(preg_match('/[^0-9]/', $mins)) { // got a range, or multiple of mins, so we return an 'Hours' label
741                                         return $value;
742                                 } else {        // got a "minutes" setting, so it will be at some o'clock.
743                                         $datef = $current_user->getUserDateTimePreferences();
744                                         return date($datef['time'], strtotime($value.':'.str_pad($mins, 2, '0', STR_PAD_LEFT)));
745                                 }
746                         case 2: // day of month
747                                 if(preg_match('/\*/', $value)) {
748                                         return $value;
749                                 } else {
750                                         return date('jS', strtotime('December '.$value));
751                                 }
752
753                         case 3: // months
754                                 return date('F', strtotime('2005-'.$value.'-01'));
755                         case 4: // days of week
756                                 return $days[$value];
757                         default:
758                                 return 'bad'; // no condition to touch this branch
759                 }
760         }
761
762         function setIntervalHumanReadable() {
763                 global $current_user;
764                 global $mod_strings;
765
766                 /* [0]:min [1]:hour [2]:day of month [3]:month [4]:day of week */
767                 $ints = $this->intervalParsed;
768                 $intVal = array('-', ',');
769                 $intSub = array($mod_strings['LBL_RANGE'], $mod_strings['LBL_AND']);
770                 $intInt = array(0 => $mod_strings['LBL_MINS'], 1 => $mod_strings['LBL_HOUR']);
771                 $tempInt = '';
772                 $iteration = '';
773
774                 foreach($ints['raw'] as $key => $interval) {
775                         if($tempInt != $iteration) {
776                                 $tempInt .= '; ';
777                         }
778                         $iteration = $tempInt;
779
780                         if($interval != '*' && $interval != '*/1') {
781                                 if(false !== strpos($interval, ',')) {
782                                         $exIndiv = explode(',', $interval);
783                                         foreach($exIndiv as $val) {
784                                                 if(false !== strpos($val, '-')) {
785                                                         $exRange = explode('-', $val);
786                                                         foreach($exRange as $valRange) {
787                                                                 if($tempInt != '') {
788                                                                         $tempInt .= $mod_strings['LBL_AND'];
789                                                                 }
790                                                                 $tempInt .= $this->handleIntervalType($key, $valRange, $ints['raw'][0], $ints['raw'][1]);
791                                                         }
792                                                 } elseif($tempInt != $iteration) {
793                                                         $tempInt .= $mod_strings['LBL_AND'];
794                                                 }
795                                                 $tempInt .= $this->handleIntervalType($key, $val, $ints['raw'][0], $ints['raw'][1]);
796                                         }
797                                 } elseif(false !== strpos($interval, '-')) {
798                                         $exRange = explode('-', $interval);
799                                         $tempInt .= $mod_strings['LBL_FROM'];
800                                         $check = $tempInt;
801
802                                         foreach($exRange as $val) {
803                                                 if($tempInt == $check) {
804                                                         $tempInt .= $this->handleIntervalType($key, $val, $ints['raw'][0], $ints['raw'][1]);
805                                                         $tempInt .= $mod_strings['LBL_RANGE'];
806
807                                                 } else {
808                                                         $tempInt .= $this->handleIntervalType($key, $val, $ints['raw'][0], $ints['raw'][1]);
809                                                 }
810                                         }
811
812                                 } elseif(false !== strpos($interval, '*/')) {
813                                         $tempInt .= $mod_strings['LBL_EVERY'];
814                                         $tempInt .= $this->handleIntervalType($key, $interval, $ints['raw'][0], $ints['raw'][1]);
815                                 } else {
816                                         $tempInt .= $this->handleIntervalType($key, $interval, $ints['raw'][0], $ints['raw'][1]);
817                                 }
818                         }
819                 } // end foreach()
820
821                 if($tempInt == '') {
822                         $this->intervalHumanReadable = $mod_strings['LBL_OFTEN'];
823                 } else {
824                         $tempInt = trim($tempInt);
825                         if(';' == substr($tempInt, (strlen($tempInt)-1), strlen($tempInt))) {
826                                 $tempInt = substr($tempInt, 0, (strlen($tempInt)-1));
827                         }
828                         $this->intervalHumanReadable = $tempInt;
829                 }
830         }
831
832
833         /* take an integer and return its suffix */
834         function setStandardArraysAttributes() {
835                 global $mod_strings;
836                 global $app_list_strings; // using from month _dom list
837
838                 $suffArr = array('','st','nd','rd');
839                 for($i=1; $i<32; $i++) {
840                         if($i > 3 && $i < 21) {
841                                 $this->suffixArray[$i] = $i."th";
842                         } elseif (substr($i,-1,1) < 4 && substr($i,-1,1) > 0) {
843                                 $this->suffixArray[$i] = $i.$suffArr[substr($i,-1,1)];
844                         } else {
845                                 $this->suffixArray[$i] = $i."th";
846                         }
847                         $this->datesArray[$i] = $i;
848                 }
849
850                 $this->dayInt = array('*',1,2,3,4,5,6,7);
851                 $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']);
852                 $this->monthsInt = array(0,1,2,3,4,5,6,7,8,9,10,11,12);
853                 $this->monthsLabel = $app_list_strings['dom_cal_month_long'];
854                 $this->metricsVar = array("*", "/", "-", ",");
855                 $this->metricsVal = array(' every ','',' thru ',' and ');
856         }
857
858         /**
859          *  takes the serialized interval string and renders it into an array
860          */
861         function parseInterval() {
862                 global $metricsVar;
863                 $ws = array(' ', '\r','\t');
864                 $blanks = array('','','');
865
866                 $intv = $this->job_interval;
867                 $rawValues = explode('::', $intv);
868                 $rawProcessed = str_replace($ws,$blanks,$rawValues); // strip all whitespace
869
870                 $hours = $rawValues[1].':::'.$rawValues[0];
871                 $months = $rawValues[3].':::'.$rawValues[2];
872
873                 $intA = array ( 'raw' => $rawProcessed,
874                                                 'hours' => $hours,
875                                                 'months' => $months,
876                                                 );
877
878                 $this->intervalParsed = $intA;
879         }
880
881         /**
882          * soft-deletes all job logs older than 24 hours
883          */
884         function cleanJobLog() {
885                 $this->db->query('DELETE FROM schedulers_times WHERE date_entered < '.db_convert('\''.gmdate($GLOBALS['timedate']->get_db_date_time_format(), strtotime('-24 hours')).'\'', 'datetime').'');
886         }
887
888         /**
889          * checks for cURL libraries
890          */
891         function checkCurl() {
892                 global $mod_strings;
893
894                 if(!function_exists('curl_init')) {
895                         echo '
896                         <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
897                                 <tr height="20">
898                                         <th width="25%" colspan="2"><slot>
899                                                 '.$mod_strings['LBL_WARN_CURL_TITLE'].'
900                                         </slot></td>
901                                 </tr>
902                                 <tr class="oddListRowS1" >
903                                         <td scope="row" valign=TOP width="20%"><slot>
904                                                 '.$mod_strings['LBL_WARN_CURL'].'
905                                         <td scope="row" valign=TOP width="80%"><slot>
906                                                 <span class=error>'.$mod_strings['LBL_WARN_NO_CURL'].'</span>
907                                         </slot></td>
908                                 </tr>
909                         </table>
910                         <br>';
911                 }
912         }
913
914         function displayCronInstructions() {
915                 global $mod_strings;
916                 global $sugar_config;
917                 $error = '';
918                 if (!isset($_SERVER['Path'])) {
919             $_SERVER['Path'] = getenv('Path');
920         }
921         if(is_windows()) {
922                         if(isset($_SERVER['Path']) && !empty($_SERVER['Path'])) { // IIS IUSR_xxx may not have access to Path or it is not set
923                                 if(!strpos($_SERVER['Path'], 'php')) {
924                                         $error = '<em>'.$mod_strings['LBL_NO_PHP_CLI'].'</em>';
925                                 }
926                         }
927                 } else {
928                         if(isset($_SERVER['Path']) && !empty($_SERVER['Path'])) { // some Linux servers do not make this available
929                                 if(!strpos($_SERVER['PATH'], 'php')) {
930                                         $error = '<em>'.$mod_strings['LBL_NO_PHP_CLI'].'</em>';
931                                 }
932                         }
933                 }
934
935
936
937                 if(is_windows()) {
938                         echo '<br>';
939                         echo '
940                                 <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
941                                 <tr height="20">
942                                         <th><slot>
943                                                 '.$mod_strings['LBL_CRON_INSTRUCTIONS_WINDOWS'].'
944                                         </slot></th>
945                                 </tr>
946                                 <tr class="evenListRowS1">
947                                         <td scope="row" valign="top" width="70%"><slot>
948                                                 '.$mod_strings['LBL_CRON_WINDOWS_DESC'].'<br>
949                                                 <b>cd '.realpath('./').'<br>
950                                                 php.exe -f cron.php</b>
951                                         </slot></td>
952                                 </tr>
953                         </table>';
954                 } else {
955                         echo '<br>';
956                         echo '
957                                 <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
958                                 <tr height="20">
959                                         <th><slot>
960                                                 '.$mod_strings['LBL_CRON_INSTRUCTIONS_LINUX'].'
961                                         </slot></th>
962                                 </tr>
963                                 <tr>
964                                         <td scope="row" valign=TOP class="oddListRowS1" bgcolor="#fdfdfd" width="70%"><slot>
965                                                 '.$mod_strings['LBL_CRON_LINUX_DESC'].'<br>
966                                                 <b>*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;
967                                                 cd '.realpath('./').'; php -f cron.php > /dev/null 2>&1</b>
968                                                 <br>'.$error.'
969                                         </slot></td>
970                                 </tr>
971                         </table>';
972                 }
973         }
974
975         /**
976          * Archives schedulers of the same functionality, then instantiates new
977          * ones.
978          */
979         function rebuildDefaultSchedulers() {
980                 global $mod_strings;
981                 // truncate scheduler-related tables
982                 $this->db->query('DELETE FROM schedulers');
983                 $this->db->query('DELETE FROM schedulers_times');
984
985
986         $sched3 = new Scheduler();
987         $sched3->name               = $mod_strings['LBL_OOTB_TRACKER'];
988         $sched3->job                = 'function::trimTracker';
989         $sched3->date_time_start    = create_date(2005,1,1) . ' ' . create_time(0,0,1);
990         $sched3->date_time_end      = create_date(2020,12,31) . ' ' . create_time(23,59,59);
991         $sched3->job_interval       = '0::2::1::*::*';
992         $sched3->status             = 'Active';
993         $sched3->created_by         = '1';
994         $sched3->modified_user_id   = '1';
995         $sched3->catch_up           = '1';
996         $sched3->save();
997                 $sched4 = new Scheduler();
998                 $sched4->name                           = $mod_strings['LBL_OOTB_IE'];
999                 $sched4->job                            = 'function::pollMonitoredInboxes';
1000                 $sched4->date_time_start        = create_date(2005,1,1) . ' ' . create_time(0,0,1);
1001                 $sched4->date_time_end          = create_date(2020,12,31) . ' ' . create_time(23,59,59);
1002                 $sched4->job_interval           = '*::*::*::*::*';
1003                 $sched4->status                         = 'Active';
1004                 $sched4->created_by                     = '1';
1005                 $sched4->modified_user_id       = '1';
1006                 $sched4->catch_up                       = '0';
1007                 $sched4->save();
1008
1009                 $sched5 = new Scheduler();
1010                 $sched5->name                           = $mod_strings['LBL_OOTB_BOUNCE'];
1011                 $sched5->job                            = 'function::pollMonitoredInboxesForBouncedCampaignEmails';
1012                 $sched5->date_time_start        = create_date(2005,1,1) . ' ' . create_time(0,0,1);
1013                 $sched5->date_time_end          = create_date(2020,12,31) . ' ' . create_time(23,59,59);
1014                 $sched5->job_interval           = '0::2-6::*::*::*';
1015                 $sched5->status                         = 'Active';
1016                 $sched5->created_by                     = '1';
1017                 $sched5->modified_user_id       = '1';
1018                 $sched5->catch_up                       = '1';
1019                 $sched5->save();
1020
1021                 $sched6 = new Scheduler();
1022                 $sched6->name                           = $mod_strings['LBL_OOTB_CAMPAIGN'];
1023                 $sched6->job                            = 'function::runMassEmailCampaign';
1024                 $sched6->date_time_start        = create_date(2005,1,1) . ' ' . create_time(0,0,1);
1025                 $sched6->date_time_end          = create_date(2020,12,31) . ' ' . create_time(23,59,59);
1026                 $sched6->job_interval           = '0::2-6::*::*::*';
1027                 $sched6->status                         = 'Active';
1028                 $sched6->created_by                     = '1';
1029                 $sched6->modified_user_id       = '1';
1030                 $sched6->catch_up                       = '1';
1031                 $sched6->save();
1032
1033
1034         $sched7 = new Scheduler();
1035         $sched7->name               = $mod_strings['LBL_OOTB_PRUNE'];
1036         $sched7->job                = 'function::pruneDatabase';
1037         $sched7->date_time_start    = create_date(2005,1,1) . ' ' . create_time(0,0,1);
1038         $sched7->date_time_end      = create_date(2020,12,31) . ' ' . create_time(23,59,59);
1039         $sched7->job_interval       = '0::4::1::*::*';
1040         $sched7->status             = 'Inactive';
1041         $sched7->created_by         = '1';
1042         $sched7->modified_user_id   = '1';
1043         $sched7->catch_up           = '0';
1044         $sched7->save();
1045
1046
1047
1048
1049
1050         }
1051
1052         ////    END SCHEDULER HELPER FUNCTIONS
1053         ///////////////////////////////////////////////////////////////////////////
1054
1055
1056         ///////////////////////////////////////////////////////////////////////////
1057         ////    STANDARD SUGARBEAN OVERRIDES
1058         /**
1059          * function overrides the one in SugarBean.php
1060          */
1061         function create_export_query($order_by, $where, $show_deleted = 0) {
1062                 return $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted = 0);
1063         }
1064
1065         /**
1066          * function overrides the one in SugarBean.php
1067          */
1068
1069         /**
1070          * function overrides the one in SugarBean.php
1071          */
1072         function fill_in_additional_list_fields() {
1073                 $this->fill_in_additional_detail_fields();
1074         }
1075
1076         /**
1077          * function overrides the one in SugarBean.php
1078          */
1079         function fill_in_additional_detail_fields() {
1080     }
1081
1082         /**
1083          * function overrides the one in SugarBean.php
1084          */
1085         function get_list_view_data(){
1086                 global $mod_strings;
1087                 $temp_array = $this->get_list_view_array();
1088         $temp_array["ENCODED_NAME"]=$this->name;
1089         $this->parseInterval();
1090         $this->setIntervalHumanReadable();
1091         $temp_array['JOB_INTERVAL'] = $this->intervalHumanReadable;
1092         if($this->date_time_end == '2020-12-31 23:59' || $this->date_time_end == '') {
1093                 $temp_array['DATE_TIME_END'] = $mod_strings['LBL_PERENNIAL'];
1094         }
1095         $this->created_by_name = get_assigned_user_name($this->created_by);
1096                 $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
1097         return $temp_array;
1098
1099         }
1100
1101         /**
1102          * returns the bean name - overrides SugarBean's
1103          */
1104         function get_summary_text() {
1105                 return $this->name;
1106         }
1107         ////    END STANDARD SUGARBEAN OVERRIDES
1108         ///////////////////////////////////////////////////////////////////////////
1109 } // end class definition
1110 ?>