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?>