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