1<?
2/***************************************************************************
3
4pseudo-cron v1.3
5(c) 2003,2004 Kai Blankenhorn
6www.bitfolge.de/pseudocron
7kaib@bitfolge.de
8
9
10This program is free software; you can redistribute it and/or
11modify it under the terms of the GNU General Public License
12as published by the Free Software Foundation; either version 2
13of the License, or (at your option) any later version.
14
15This program is distributed in the hope that it will be useful,
16but WITHOUT ANY WARRANTY; without even the implied warranty of
17MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23
24****************************************************************************
25
26
27Usually regular tasks like backup up the site's database are run using cron
28jobs. With cron jobs, you can exactly plan when a certain command is to be
29executed. But most homepage owners can't create cron jobs on their web
30server � providers demand some extra money for that.
31The only thing that's certain to happen quite regularly on a web page are
32page requests. This is where pseudo-cron comes into play: With every page
33request it checks if any cron jobs should have been run since the previous
34request. If there are, they are run and logged.
35
36Pseudo-cron uses a syntax very much like the Unix cron's one. For an
37overview of the syntax used, see a page of the UNIXGEEKS. The syntax
38pseudo-cron uses is different from the one described on that page in
39the following points:
40
41  -  there is no user column
42  -  the executed command has to be an include()able file (which may contain further PHP code)
43
44
45All job definitions are made in a text file on the server with a
46user-definable name. A valid command line in this file is, for example:
47
48*	2	1,15	*	*	samplejob.inc.php
49
50This runs samplejob.inc.php at 2am on the 1st and 15th of each month.
51
52
53Features:
54  -  runs any PHP script
55  -  periodical or time-controlled script execution
56  -  logs all executed jobs
57  -  can be run from an IMG tag in an HTML page
58  -  follow Unix cron syntax for crontabs
59
60
61Usage:
62  -  Modify the variables in the config section below to match your server.
63  -  Write a PHP script that does the job you want to be run regularly. Be
64     sure that any paths in it are relative to pseudo-cron.
65  -  Set up your crontab file with your script
66	-  put an include("pseudo-cron.inc.php"); statement somewhere in your most
67	   accessed page or call pseudo-cron-image.php from an HTML img tag
68  -  Wait for the next scheduled run :)
69
70
71Note:
72You can log messages to pseudo-cron's log file from cron jobs by calling
73     $this->logMessage("log a message");
74
75
76
77Release notes for v1.2.2:
78
79This release changed the way cron jobs are called. The file paths you specify in
80the crontab file are now relative to the location of pseudo-cron.inc.php, instead
81of to the calling script. Example: If /include/pseudo-cron.inc.php is included
82in /index.php and your cronjobs are in /include/cronjobs, then your crontab file
83looked like this:
84
8510	1	*	*	*	include/cronjobs/dosomething.php	# do something
86
87Now you have to change it to
88
8910	1	*	*	*	cronjobs/dosomething.php	# do something
90
91After you install the new version, each of your cronjobs will be run once,
92and the .job files will have different names than before.
93
94
95***************************************************************************/
96
97  // || PLEASE NOTE:
98  // || all paths used here and in cron scripts
99  // || must be absolute or relative to pseudo-cron.inc.php!
100  // ||
101  // || To easily use absolute paths, have a look at how the
102  // || crontab location is defined below.
103
104define("PC_MINUTE",	1);
105define("PC_HOUR",	2);
106define("PC_DOM",	3);
107define("PC_MONTH",	4);
108define("PC_DOW",	5);
109define("PC_CMD",	7);
110define("PC_COMMENT",	8);
111define("PC_CRONLINE", 20);
112
113// set some info about Dokuwiki
114define('DOKU_INC', realpath(dirname(__FILE__).'/../../../../').'/');
115define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
116
117class cronojob {
118
119  /****************************************/
120  /*		config section					*/
121  /****************************************/
122
123  // The string that contains the job descriptions.
124  // One job for line
125
126  // #scheduled jobs
127  // #comments start with #
128  // #mi  h  d  m  dow  job  comment';
129
130  // For a description of the format, see http://www.unixgeeks.org/security/newbie/unix/cron-1.html
131  // and http://www.bitfolge.de/pseudocron
132  var $cronTab;
133
134  // The directory where the script can store information on completed jobs and its log file.
135  // include trailing slash
136  var $writeDir;
137
138  // Control logging, true=use log file, false=don't use log file
139  var $useLog;
140
141  // Where to send cron results.
142  var $sendLogToEmail;
143
144  // Maximum number of jobs run during one call of pseudocron.
145  // Set to a low value if your jobs take longer than a few seconds and if you scheduled them
146  // very close to each other. Set to 0 to run any number of jobs.
147  var $maxJobs = 1;
148
149  // Turn on / off debugging output
150  // DO NOT use this on live servers!
151  var $debug = false;
152
153  /****************************************/
154  /*		don't change anything here		*/
155  /****************************************/
156
157  var $resultsSummary;
158  var $pluginname;
159  var $dokuwikititle;
160
161  function cronojob($debug = false) {
162
163    $this->debug = $debug;
164    $this->writeDir = DOKU_INC."data/tmp/";
165    $this->pluginname = "cronojob";
166
167    // legge la configurazione
168    $path = DOKU_PLUGIN.$this->pluginname.'/conf/';
169    $conf = array();
170    if (@file_exists($path.'default.php')) {
171      include($path.'default.php');
172    }
173    $conf_plug = $conf;
174
175    $path = DOKU_INC.'conf/';
176    $conf = array();
177    if (@file_exists($path.'local.php')) {
178      include($path.'local.php');
179    }
180    $this->dokuwikititle = $conf['title'];
181    foreach ($conf_plug as $key => $value) {
182      if (isset($conf['plugin'][$this->pluginname][$key])) $conf_plug[$key] = $conf['plugin'][$this->pluginname][$key];
183    }
184    // legge la configurazione
185
186    $this->cronTab = $conf_plug['cronotab'];
187    $this->sendLogToEmail = $conf_plug['email'];
188    if ($conf_plug['maxjobs'] > 0) $this->maxjobs = $conf_plug['maxjobs'];
189    $this->useLog = $conf_plug['uselog'];
190
191  }
192
193  function dojobs() {
194    if ($this->debug) echo "<pre>";
195    $jobs = $this->parseCronFile($this->cronTab);
196    $jobsRun = 0;
197    for ($i=0;$i<count($jobs);$i++) {
198      if ($this->maxJobs==0 || $jobsRun<$this->maxJobs) {
199        if ($this->runJob($jobs[$i])) $jobsRun++;
200      }
201    }
202    if ($this->debug) echo "</pre>";
203
204  }
205
206  function logMessage($msg) {
207    if ($msg[strlen($msg)-1]!="\n") {
208      $msg.="\n";
209    }
210    if ($this->debug) echo $msg." ".$this->useLog;
211    $this->resultsSummary.= $msg;
212    if ($this->useLog) {
213      $logfile = $this->writeDir."pseudo-cron.log";
214      $file = fopen($logfile,"a");
215      fputs($file,date("r",time())."  ".$msg);
216      fclose($file);
217    }
218  }
219
220  function lTrimZeros($number) {
221    while ($number[0]=='0') {
222      $number = substr($number,1);
223    }
224    return $number;
225  }
226
227  function multisort(&$array, $sortby, $order='asc') {
228     foreach($array as $val) {
229         $sortarray[] = $val[$sortby];
230     }
231     $c = $array;
232     $const = $order == 'asc' ? SORT_ASC : SORT_DESC;
233     $s = array_multisort($sortarray, $const, $c, $const);
234     $array = $c;
235     return $s;
236  }
237
238  function parseElement($element, &$targetArray, $numberOfElements) {
239    $subelements = explode(",",$element);
240    for ($i=0;$i<$numberOfElements;$i++) {
241      $targetArray[$i] = $subelements[0]=="*";
242    }
243
244    for ($i=0;$i<count($subelements);$i++) {
245      if (preg_match("~^(\\*|([0-9]{1,2})(-([0-9]{1,2}))?)(/([0-9]{1,2}))?$~",$subelements[$i],$matches)) {
246        if ($matches[1]=="*") {
247          $matches[2] = 0;		// from
248          $matches[4] = $numberOfElements;		//to
249        } elseif ($matches[4]=="") {
250          $matches[4] = $matches[2];
251        }
252        if ($matches[5][0]!="/") {
253          $matches[6] = 1;		// step
254        }
255        for ($j=$this->lTrimZeros($matches[2]);$j<=$this->lTrimZeros($matches[4]);$j+=$this->lTrimZeros($matches[6])) {
256          $targetArray[$j] = TRUE;
257        }
258      }
259    }
260  }
261
262  function incDate(&$dateArr, $amount, $unit) {
263
264    if ($this->debug) echo sprintf("Increasing from %02d.%02d. %02d:%02d by %d %6s ",$dateArr[mday],$dateArr[mon],$dateArr[hours],$dateArr[minutes],$amount,$unit);
265    if ($unit=="mday") {
266      $dateArr["hours"] = 0;
267      $dateArr["minutes"] = 0;
268      $dateArr["seconds"] = 0;
269      $dateArr["mday"] += $amount;
270      $dateArr["wday"] += $amount % 7;
271      if ($dateArr["wday"]>6) {
272        $dateArr["wday"]-=7;
273      }
274
275      $months28 = Array(2);
276      $months30 = Array(4,6,9,11);
277      $months31 = Array(1,3,5,7,8,10,12);
278
279      if (
280        (in_array($dateArr["mon"], $months28) && $dateArr["mday"]==28) ||
281        (in_array($dateArr["mon"], $months30) && $dateArr["mday"]==30) ||
282        (in_array($dateArr["mon"], $months31) && $dateArr["mday"]==31)
283      ) {
284        $dateArr["mon"]++;
285        $dateArr["mday"] = 1;
286      }
287
288    } elseif ($unit=="hour") {
289      if ($dateArr["hours"]==23) {
290        $this->incDate($dateArr, 1, "mday");
291      } else {
292        $dateArr["minutes"] = 0;
293        $dateArr["seconds"] = 0;
294        $dateArr["hours"]++;
295      }
296    } elseif ($unit=="minute") {
297      if ($dateArr["minutes"]==59) {
298        $this->incDate($dateArr, 1, "hour");
299      } else {
300        $dateArr["seconds"] = 0;
301        $dateArr["minutes"]++;
302      }
303    }
304    if ($this->debug) echo sprintf("to %02d.%02d. %02d:%02d\n",$dateArr[mday],$dateArr[mon],$dateArr[hours],$dateArr[minutes]);
305  }
306
307  function getLastScheduledRunTime($job) {
308
309    $extjob = Array();
310    $this->parseElement($job[PC_MINUTE], $extjob[PC_MINUTE], 60);
311    $this->parseElement($job[PC_HOUR], $extjob[PC_HOUR], 24);
312    $this->parseElement($job[PC_DOM], $extjob[PC_DOM], 31);
313    $this->parseElement($job[PC_MONTH], $extjob[PC_MONTH], 12);
314    $this->parseElement($job[PC_DOW], $extjob[PC_DOW], 7);
315
316    $dateArr = getdate($this->getLastActualRunTime($job[PC_CMD]));
317    $minutesAhead = 0;
318    while (
319      $minutesAhead<525600 AND
320      (!$extjob[PC_MINUTE][$dateArr["minutes"]] OR
321      !$extjob[PC_HOUR][$dateArr["hours"]] OR
322      (!$extjob[PC_DOM][$dateArr["mday"]] OR !$extjob[PC_DOW][$dateArr["wday"]]) OR
323      !$extjob[PC_MONTH][$dateArr["mon"]])
324    ) {
325      if (!$extjob[PC_DOM][$dateArr["mday"]] OR !$extjob[PC_DOW][$dateArr["wday"]]) {
326        $this->incDate($dateArr,1,"mday");
327        $minutesAhead+=1440;
328        continue;
329      }
330      if (!$extjob[PC_HOUR][$dateArr["hours"]]) {
331        $this->incDate($dateArr,1,"hour");
332        $minutesAhead+=60;
333        continue;
334      }
335      if (!$extjob[PC_MINUTE][$dateArr["minutes"]]) {
336        $this->incDate($dateArr,1,"minute");
337        $minutesAhead++;
338        continue;
339      }
340    }
341
342    //if ($this->debug) print_r($dateArr);
343
344    return mktime($dateArr["hours"],$dateArr["minutes"],0,$dateArr["mon"],$dateArr["mday"],$dateArr["year"]);
345  }
346
347  function getJobFileName($jobname) {
348    $jobfile = $this->writeDir.urlencode($jobname).".job";
349    return $jobfile;
350  }
351
352  function getLastActualRunTime($jobname) {
353    $jobfile = $this->getJobFileName($jobname);
354    if (file_exists($jobfile)) {
355      return filemtime($jobfile);
356    }
357    return 0;
358  }
359
360  function markLastRun($jobname, $lastRun) {
361    $jobfile = $this->getJobFileName($jobname);
362    touch($jobfile);
363  }
364
365  function runJob($job) {
366    $this->resultsSummary = "";
367
368    $lastActual = $job["lastActual"];
369    $lastScheduled = $job["lastScheduled"];
370
371    if ($lastScheduled<time()) {
372      $this->logMessage("Running 	".$job[PC_CRONLINE]);
373      $this->logMessage(" Last run: ".date("r",$lastActual).", Last scheduled: ".date("r",$lastScheduled));
374      $e = @error_reporting(0);
375      if ($this->debug) {
376        include(DOKU_PLUGIN.$this->pluginname."/jobs/".$job[PC_CMD]);		// display errors only when debugging
377      } else {
378        @include(DOKU_PLUGIN.$this->pluginname."/jobs/".$job[PC_CMD]);		// any error messages are supressed
379      }
380      @error_reporting($e);
381      $this->markLastRun($job[PC_CMD], $lastScheduled);
382      $this->logMessage(" Completed	".$job[PC_CRONLINE]);
383      if ($this->sendLogToEmail!="") {
384        mail($this->sendLogToEmail, "[".$this->dokuwikititle."][".$this->pluginname."] ".$job[PC_COMMENT], $this->resultsSummary);
385      }
386      return true;
387    } else {
388      if ($this->debug) {
389        $this->logMessage("Skipping 	".$job[PC_CRONLINE]);
390        $this->logMessage(" Last run: ".date("r",$lastActual).", Last scheduled: ".date("r",$lastScheduled));
391        $this->logMessage(" Completed	".$job[PC_CRONLINE]);
392      }
393      return false;
394    }
395  }
396
397  function parseCronFile($cronTabFile) {
398    $file = explode("\n", $cronTabFile);
399//    $file = file($cronTabFile);
400//    $file = $cronTabFile;
401    $job = Array();
402    $jobs = Array();
403    for ($i=0;$i<count($file);$i++) {
404      if ($file[$i][0]!='#') {
405  //			old regex, without dow abbreviations:
406  //			if (preg_match("~^([-0-9,/*]+)\\s+([-0-9,/*]+)\\s+([-0-9,/*]+)\\s+([-0-9,/*]+)\\s+([-0-7,/*]+|Sun|Mon|Tue|Wen|Thu|Fri|Sat)\\s+([^#]*)(#.*)?$~i",$file[$i],$job)) {
407        if (preg_match("~^([-0-9,/*]+)\\s+([-0-9,/*]+)\\s+([-0-9,/*]+)\\s+([-0-9,/*]+)\\s+([-0-7,/*]+|(-|/|Sun|Mon|Tue|Wed|Thu|Fri|Sat)+)\\s+([^#]*)\\s*(#.*)?$~i",$file[$i],$job)) {
408          $jobNumber = count($jobs);
409          $jobs[$jobNumber] = $job;
410          if ($jobs[$jobNumber][PC_DOW][0]!='*' AND !is_numeric($jobs[$jobNumber][PC_DOW])) {
411            $jobs[$jobNumber][PC_DOW] = str_replace(
412              Array("Sun","Mon","Tue","Wed","Thu","Fri","Sat"),
413              Array(0,1,2,3,4,5,6),
414              $jobs[$jobNumber][PC_DOW]);
415          }
416          $jobs[$jobNumber][PC_CMD] = trim($job[PC_CMD]);
417          $jobs[$jobNumber][PC_COMMENT] = trim(substr($job[PC_COMMENT],1));
418          $jobs[$jobNumber][PC_CRONLINE] = $file[$i];
419        }
420        $jobfile = $this->getJobFileName($jobs[$jobNumber][PC_CMD]);
421
422        $jobs[$jobNumber]["lastActual"] = $this->getLastActualRunTime($jobs[$jobNumber][PC_CMD]);
423        $jobs[$jobNumber]["lastScheduled"] = $this->getLastScheduledRunTime($jobs[$jobNumber]);
424      }
425    }
426
427    $this->multisort($jobs, "lastScheduled");
428
429    if ($this->debug) var_dump($jobs);
430    return $jobs;
431  }
432
433  function makeImage() {
434    if (!$this->debug) {
435      $img = base64_decode("iVBORw0KGgoAAAANSUhEUgAAAFAAAAAPCAIAAAD8q9/YAAAA5ElEQVRIieVXuw7DIAw8qnwrnrt0
436      4AM6sDD7a+lAQylg4kp5VOIm63TINucYxVhrMRMWAMx8dRkngYiWNTQ/n+bog9u3oEPxuD8B3K4u
437      42xM1/DS4Ti+g3LOEylMfpoWAOWQJzIzSo0UVwc3Nd10EB0mAzKfzjnWTAMfnA8up0mllIxSU8YD
438      mUbTPdVzGLKf8m6TTBj42UUqcZd12L2gocMtthweZ1W2MTYTQifKkgSHK+RhVr9euegqZWmgpMF3
439      z61Mw0AYKGOtZeZJ3mEimu5Zmq7h9RuWt9EAyuXxVzCz/S29AGJYqkfnR9EBAAAAAElFTkSuQmCC");
440      //$img = base64_decode("R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==");
441
442      Header("Content-Type: image/gif");
443      Header('Content-Length: '.strlen($img));
444      echo $img;
445    }
446  }
447
448}
449
450?>