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