4 # FILE: ApplicationFramework.php
6 # Part of the ScoutLib application support library
7 # Copyright 2009-2012 Edward Almasy and Internet Scout
8 # http://scout.wisc.edu
17 # ---- PUBLIC INTERFACE --------------------------------------------------
28 function __construct($ObjectDirectories = NULL)
30 # save execution start time
31 $this->ExecutionStartTime = microtime(TRUE);
33 # begin/restore PHP session
34 $SessionPath = isset($_SERVER[
"REQUEST_URI"])
35 ? dirname($_SERVER[
"REQUEST_URI"])
36 : isset($_SERVER[
"SCRIPT_NAME"])
37 ? dirname($_SERVER[
"SCRIPT_NAME"])
38 : isset($_SERVER[
"PHP_SELF"])
39 ? dirname($_SERVER[
"PHP_SELF"])
41 $SessionDomain = isset($_SERVER[
"SERVER_NAME"]) ? $_SERVER[
"SERVER_NAME"]
42 : isset($_SERVER[
"HTTP_HOST"]) ? $_SERVER[
"HTTP_HOST"]
44 session_set_cookie_params(
45 self::$SessionLifetime, $SessionPath, $SessionDomain);
48 # save object directory search list
51 # set up object file autoloader
52 $this->SetUpObjectAutoloading();
54 # set up function to output any buffered text in case of crash
55 register_shutdown_function(array($this,
"OnCrash"));
57 # set up our internal environment
60 # set up our exception handler
61 set_exception_handler(array($this,
"GlobalExceptionHandler"));
63 # load our settings from database
64 $this->LoadSettings();
66 # set PHP maximum execution time
69 # register events we handle internally
81 # if template location cache is flagged to be saved
82 if ($this->SaveTemplateLocationCache)
84 # write template location cache out and update cache expiration
85 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
86 .
" SET TemplateLocationCache = '"
87 .addslashes(serialize(
88 $this->Settings[
"TemplateLocationCache"])).
"',"
89 .
" TemplateLocationCacheExpiration = "
91 .$this->Settings[
"TemplateLocationCacheInterval"]
101 function GlobalExceptionHandler($Exception)
103 # display exception info
104 $Location = $Exception->getFile().
"[".$Exception->getLine().
"]";
105 ?><table width=
"100%" cellpadding=
"5"
106 style=
"border: 2px solid #666666; background: #CCCCCC;
107 font-family: Courier New, Courier, monospace;
108 margin-top: 10px;"><tr><td>
109 <div style=
"color: #666666;">
110 <span style=
"font-size: 150%;">
111 <b>Uncaught Exception</b></span><br />
112 <b>
Message:</b> <i><?
PHP print $Exception->getMessage(); ?></i><br />
113 <b>Location:</b> <i><?
PHP print $Location; ?></i><br />
115 <blockquote><pre><?
PHP print $Exception->getTraceAsString();
116 ?></pre></blockquote>
118 </td></tr></table><?
PHP
120 # log exception if possible
121 $LogMsg =
"Uncaught exception (".$Exception->getMessage().
").";
122 $this->
LogError(self::LOGLVL_ERROR, $LogMsg);
133 # for each supplied directory
134 foreach ($Dirs as $Location => $Prefix)
136 # make sure directory has trailing slash
137 $Location = $Location
138 .((substr($Location, -1) !=
"/") ?
"/" :
"");
140 # add directory to directory list
141 self::$ObjectDirectories = array_merge(
142 array($Location => $Prefix),
143 self::$ObjectDirectories);
168 # add directories to existing image directory list
169 $this->ImageDirList = $this->AddToDirList(
170 $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
195 # add directories to existing image directory list
196 $this->IncludeDirList = $this->AddToDirList(
197 $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
221 # add directories to existing image directory list
222 $this->InterfaceDirList = $this->AddToDirList(
223 $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
247 # add directories to existing image directory list
248 $this->FunctionDirList = $this->AddToDirList(
249 $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
259 $this->BrowserDetectFunc = $DetectionFunc;
270 if (is_callable($Callback))
272 $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
284 if ($NewInterval >= 0)
286 $this->Settings[
"TemplateLocationCacheInterval"] = $NewInterval;
287 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
288 .
" SET TemplateLocationCacheInterval = '"
289 .intval($NewInterval).
"'");
291 return $this->Settings[
"TemplateLocationCacheInterval"];
301 if ($NewValue !== NULL)
303 self::$SessionLifetime = $NewValue;
305 return self::$SessionLifetime;
314 # sanitize incoming page name and save local copy
315 $PageName = preg_replace(
"/[^a-zA-Z0-9_.-]/",
"", $PageName);
316 $this->PageName = $PageName;
318 # buffer any output from includes or PHP file
321 # include any files needed to set up execution environment
322 foreach ($this->EnvIncludes as $IncludeFile)
324 include($IncludeFile);
328 $this->
SignalEvent(
"EVENT_PAGE_LOAD", array(
"PageName" => $PageName));
330 # signal PHP file load
331 $SignalResult = $this->
SignalEvent(
"EVENT_PHP_FILE_LOAD", array(
332 "PageName" => $PageName));
334 # if signal handler returned new page name value
335 $NewPageName = $PageName;
336 if (($SignalResult[
"PageName"] != $PageName)
337 && strlen($SignalResult[
"PageName"]))
339 # if new page name value is page file
340 if (file_exists($SignalResult[
"PageName"]))
342 # use new value for PHP file name
343 $PageFile = $SignalResult[
"PageName"];
347 # use new value for page name
348 $NewPageName = $SignalResult[
"PageName"];
352 # if we do not already have a PHP file
353 if (!isset($PageFile))
355 # look for PHP file for page
356 $OurPageFile =
"pages/".$NewPageName.
".php";
357 $LocalPageFile =
"local/pages/".$NewPageName.
".php";
358 $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
359 : (file_exists($OurPageFile) ? $OurPageFile
360 :
"pages/".$this->DefaultPage.
".php");
366 # save buffered output to be displayed later after HTML file loads
367 $PageOutput = ob_get_contents();
370 # signal PHP file load is complete
372 $Context[
"Variables"] = get_defined_vars();
374 array(
"PageName" => $PageName,
"Context" => $Context));
375 $PageCompleteOutput = ob_get_contents();
378 # set up for possible TSR (Terminate and Stay Resident :))
379 $ShouldTSR = $this->PrepForTSR();
381 # if PHP file indicated we should autorefresh to somewhere else
382 if ($this->JumpToPage)
384 if (!strlen(trim($PageOutput)))
388 <meta http-equiv=
"refresh" content=
"0; URL=<?PHP
389 print($this->JumpToPage); ?>">
391 <body bgcolor=
"white">
396 # else if HTML loading is not suppressed
397 elseif (!$this->SuppressHTML)
399 # set content-type to get rid of diacritic errors
400 header(
"Content-Type: text/html; charset="
403 # load common HTML file (defines common functions) if available
404 $CommonHtmlFile = $this->FindFile($this->IncludeDirList,
405 "Common", array(
"tpl",
"html"));
406 if ($CommonHtmlFile) { include($CommonHtmlFile); }
409 $this->LoadUIFunctions();
411 # begin buffering content
414 # signal HTML file load
415 $SignalResult = $this->
SignalEvent(
"EVENT_HTML_FILE_LOAD", array(
416 "PageName" => $PageName));
418 # if signal handler returned new page name value
419 $NewPageName = $PageName;
420 $PageContentFile = NULL;
421 if (($SignalResult[
"PageName"] != $PageName)
422 && strlen($SignalResult[
"PageName"]))
424 # if new page name value is HTML file
425 if (file_exists($SignalResult[
"PageName"]))
427 # use new value for HTML file name
428 $PageContentFile = $SignalResult[
"PageName"];
432 # use new value for page name
433 $NewPageName = $SignalResult[
"PageName"];
437 # load page content HTML file if available
438 if ($PageContentFile === NULL)
440 $PageContentFile = $this->FindFile(
441 $this->InterfaceDirList, $NewPageName,
442 array(
"tpl",
"html"));
444 if ($PageContentFile)
446 include($PageContentFile);
450 print
"<h2>ERROR: No HTML/TPL template found"
451 .
" for this page.</h2>";
454 # signal HTML file load complete
455 $SignalResult = $this->
SignalEvent(
"EVENT_HTML_FILE_LOAD_COMPLETE");
457 # stop buffering and save output
458 $PageContentOutput = ob_get_contents();
461 # load page start HTML file if available
463 $PageStartFile = $this->FindFile($this->IncludeDirList,
"Start",
464 array(
"tpl",
"html"), array(
"StdPage",
"StandardPage"));
465 if ($PageStartFile) { include($PageStartFile); }
466 $PageStartOutput = ob_get_contents();
469 # load page end HTML file if available
471 $PageEndFile = $this->FindFile($this->IncludeDirList,
"End",
472 array(
"tpl",
"html"), array(
"StdPage",
"StandardPage"));
473 if ($PageEndFile) { include($PageEndFile); }
474 $PageEndOutput = ob_get_contents();
477 # get list of any required files not loaded
478 $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
480 # if a browser detection function has been made available
481 if (is_callable($this->BrowserDetectFunc))
483 # call function to get browser list
484 $Browsers = call_user_func($this->BrowserDetectFunc);
486 # for each required file
487 $NewRequiredFiles = array();
488 foreach ($RequiredFiles as $File)
490 # if file name includes browser keyword
491 if (preg_match(
"/%BROWSER%/", $File))
494 foreach ($Browsers as $Browser)
496 # substitute in browser name and add to new file list
497 $NewRequiredFiles[] = preg_replace(
498 "/%BROWSER%/", $Browser, $File);
503 # add to new file list
504 $NewRequiredFiles[] = $File;
507 $RequiredFiles = $NewRequiredFiles;
510 # for each required file
511 foreach ($RequiredFiles as $File)
513 # locate specific file to use
514 $FilePath = $this->
GUIFile($File);
519 # determine file type
520 $NamePieces = explode(
".", $File);
521 $FileSuffix = strtolower(array_pop($NamePieces));
523 # add file to HTML output based on file type
524 $FilePath = htmlspecialchars($FilePath);
528 $Tag =
'<script type="text/javascript" src="'
529 .$FilePath.
'"></script>';
530 $PageEndOutput = preg_replace(
531 "#</body>#i", $Tag.
"\n</body>", $PageEndOutput, 1);
535 $Tag =
'<link rel="stylesheet" type="text/css"'
536 .
' media="all" href="'.$FilePath.
'">';
537 $PageStartOutput = preg_replace(
538 "#</head>#i", $Tag.
"\n</head>", $PageStartOutput, 1);
545 print $PageStartOutput.$PageContentOutput.$PageEndOutput;
548 # run any post-processing routines
549 foreach ($this->PostProcessingFuncs as $Func)
551 call_user_func_array($Func[
"FunctionName"], $Func[
"Arguments"]);
554 # write out any output buffered from page code execution
555 if (strlen($PageOutput))
557 if (!$this->SuppressHTML)
559 ?><table width=
"100%" cellpadding=
"5"
560 style=
"border: 2px solid #666666; background: #CCCCCC;
561 font-family: Courier New, Courier, monospace;
562 margin-top: 10px;"><tr><td><?
PHP
564 if ($this->JumpToPage)
566 ?><div style=
"color: #666666;"><span style=
"font-size: 150%;">
567 <b>Page Jump Aborted</b></span>
568 (because of error or other unexpected output)<br />
570 <i><?
PHP print($this->JumpToPage); ?></i></div><?
PHP
573 if (!$this->SuppressHTML)
575 ?></td></tr></table><?
PHP
579 # write out any output buffered from the page code execution complete signal
580 if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
582 print $PageCompleteOutput;
585 # execute callbacks that should not have their output buffered
586 foreach ($this->UnbufferedCallbacks as $Callback)
588 call_user_func_array($Callback[0], $Callback[1]);
591 # terminate and stay resident (TSR!) if indicated and HTML has been output
592 # (only TSR if HTML has been output because otherwise browsers will misbehave)
593 if ($ShouldTSR) { $this->LaunchTSR(); }
603 return $this->PageName;
614 if (!is_null($Page) && (strpos($Page,
"?") === FALSE)
615 && ((strpos($Page,
"=") !== FALSE)
616 || ((stripos($Page,
".php") === FALSE)
617 && (stripos($Page,
".htm") === FALSE)
618 && (strpos($Page,
"/") === FALSE)))
619 && (stripos($Page,
"http://") !== 0)
620 && (stripos($Page,
"https://") !== 0))
622 $this->JumpToPage =
"index.php?P=".$Page;
626 $this->JumpToPage = $Page;
636 return ($this->JumpToPage === NULL) ? FALSE : TRUE;
650 if ($NewSetting !== NULL) { $this->
HtmlCharset = $NewSetting; }
651 return $this->HtmlCharset;
662 $this->SuppressHTML = $NewSetting;
673 if ($UIName !== NULL)
675 $this->ActiveUI = preg_replace(
"/^SPTUI--/",
"", $UIName);
677 return $this->ActiveUI;
696 &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
697 &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
698 &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
700 $FuncIndex = count($this->PostProcessingFuncs);
701 $this->PostProcessingFuncs[$FuncIndex][
"FunctionName"] = $FunctionName;
702 $this->PostProcessingFuncs[$FuncIndex][
"Arguments"] = array();
704 while (isset(${
"Arg".$Index}) && (${
"Arg".$Index} !== self::NOVALUE))
706 $this->PostProcessingFuncs[$FuncIndex][
"Arguments"][$Index]
719 $this->EnvIncludes[] = $FileName;
730 # determine which location to search based on file suffix
731 $FileIsImage = preg_match(
"/\.(gif|jpg|png)$/", $FileName);
732 $DirList = $FileIsImage ? $this->ImageDirList : $this->IncludeDirList;
735 $FoundFileName = $this->FindFile($DirList, $FileName);
737 # add non-image files to list of found files (used for required files loading)
738 if (!$FileIsImage) { $this->FoundUIFiles[] = basename($FoundFileName); }
740 # return file name to caller
741 return $FoundFileName;
755 $FullFileName = $this->
GUIFile($FileName);
756 if ($FullFileName) { print($FullFileName); }
768 $this->AdditionalRequiredUIFiles[] = $FileName;
781 # if specified function is not currently available
782 if (!is_callable($Callback))
784 # if function info looks legal
785 if (is_string($Callback) && strlen($Callback))
787 # start with function directory list
788 $Locations = $this->FunctionDirList;
790 # add object directories to list
791 $Locations = array_merge($Locations, self::$ObjectDirectories);
793 # look for function file
794 $FunctionFileName = $this->FindFile($Locations,
"F-".$Callback,
795 array(
"php",
"html"));
797 # if function file was found
798 if ($FunctionFileName)
801 include_once($FunctionFileName);
805 # log error indicating function load failed
806 $this->
LogError(self::LOGLVL_ERROR,
"Unable to load function"
807 .
" for callback \"".$Callback.
"\".");
812 # log error indicating specified function info was bad
813 $this->
LogError(self::LOGLVL_ERROR,
"Unloadable callback value"
815 .
" passed to AF::LoadFunction().");
819 # report to caller whether function load succeeded
820 return is_callable($Callback);
829 return microtime(TRUE) - $this->ExecutionStartTime;
847 # HTACCESS_SUPPORT is set in the .htaccess file
848 return isset($_SERVER[
"HTACCESS_SUPPORT"]);
864 # if error level is at or below current logging level
865 if ($this->Settings[
"LoggingLevel"] >= $Level)
867 # attempt to log error message
870 # if logging attempt failed and level indicated significant error
871 if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
873 # throw exception about inability to log error
874 static $AlreadyThrewException = FALSE;
875 if (!$AlreadyThrewException)
877 $AlreadyThrewException = TRUE;
878 throw new Exception(
"Unable to log error (".$Level.
": ".$Msg.
").");
882 # report to caller whether message was logged
887 # report to caller that message was not logged
905 # if message level is at or below current logging level
906 if ($this->Settings[
"LoggingLevel"] >= $Level)
908 # attempt to open log file
909 $FHndl = @fopen(
"local/logs/cwis.log",
"a");
911 # if log file could not be open
912 if ($FHndl === FALSE)
914 # report to caller that message was not logged
920 $ErrorAbbrevs = array(
921 self::LOGLVL_FATAL =>
"FTL",
922 self::LOGLVL_ERROR =>
"ERR",
923 self::LOGLVL_WARNING =>
"WRN",
924 self::LOGLVL_INFO =>
"INF",
925 self::LOGLVL_DEBUG =>
"DBG",
926 self::LOGLVL_TRACE =>
"TRC",
928 $LogEntry = date(
"Y-m-d H:i:s")
929 .
" ".($this->RunningInBackground ?
"B" :
"F")
930 .
" ".$ErrorAbbrevs[$Level]
934 $Success = fputs($FHndl, $LogEntry.
"\n");
939 # report to caller whether message was logged
940 return ($Success === FALSE) ? FALSE : TRUE;
945 # report to caller that message was not logged
973 # if new logging level was specified
974 if ($NewValue !== NULL)
976 # constrain new level to within legal bounds and store locally
977 $this->Settings[
"LoggingLevel"] = max(min($NewValue, 6), 1);
979 # save new logging level in database
980 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
981 .
" SET LoggingLevel = "
982 .intval($this->Settings[
"LoggingLevel"]));
985 # report current logging level to caller
986 return $this->Settings[
"LoggingLevel"];
1024 # ---- Event Handling ----------------------------------------------------
1070 # convert parameters to array if not already in that form
1071 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1072 : array($EventsOrEventName => $Type);
1075 foreach ($Events as $Name => $Type)
1077 # store event information
1078 $this->RegisteredEvents[$Name][
"Type"] = $Type;
1079 $this->RegisteredEvents[$Name][
"Hooks"] = array();
1090 return array_key_exists($EventName, $this->RegisteredEvents)
1107 function HookEvent($EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
1109 # convert parameters to array if not already in that form
1110 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1111 : array($EventsOrEventName => $Callback);
1115 foreach ($Events as $EventName => $EventCallback)
1117 # if callback is valid
1118 if (is_callable($EventCallback))
1120 # if this is a periodic event we process internally
1121 if (isset($this->PeriodicEvents[$EventName]))
1124 $this->ProcessPeriodicEvent($EventName, $EventCallback);
1126 # if specified event has been registered
1127 elseif (isset($this->RegisteredEvents[$EventName]))
1129 # add callback for event
1130 $this->RegisteredEvents[$EventName][
"Hooks"][]
1131 = array(
"Callback" => $EventCallback,
"Order" => $Order);
1133 # sort callbacks by order
1134 if (count($this->RegisteredEvents[$EventName][
"Hooks"]) > 1)
1136 usort($this->RegisteredEvents[$EventName][
"Hooks"],
1137 array(
"ApplicationFramework",
"HookEvent_OrderCompare"));
1151 # report to caller whether all callbacks were hooked
1154 private static function HookEvent_OrderCompare($A, $B)
1156 if ($A[
"Order"] == $B[
"Order"]) {
return 0; }
1157 return ($A[
"Order"] < $B[
"Order"]) ? -1 : 1;
1170 $ReturnValue = NULL;
1172 # if event has been registered
1173 if (isset($this->RegisteredEvents[$EventName]))
1175 # set up default return value (if not NULL)
1176 switch ($this->RegisteredEvents[$EventName][
"Type"])
1178 case self::EVENTTYPE_CHAIN:
1179 $ReturnValue = $Parameters;
1182 case self::EVENTTYPE_NAMED:
1183 $ReturnValue = array();
1187 # for each callback for this event
1188 foreach ($this->RegisteredEvents[$EventName][
"Hooks"] as $Hook)
1191 $Callback = $Hook[
"Callback"];
1192 $Result = ($Parameters !== NULL)
1193 ? call_user_func_array($Callback, $Parameters)
1194 : call_user_func($Callback);
1196 # process return value based on event type
1197 switch ($this->RegisteredEvents[$EventName][
"Type"])
1199 case self::EVENTTYPE_CHAIN:
1200 if ($Result !== NULL)
1202 foreach ($Parameters as $Index => $Value)
1204 if (array_key_exists($Index, $Result))
1206 $Parameters[$Index] = $Result[$Index];
1209 $ReturnValue = $Parameters;
1213 case self::EVENTTYPE_FIRST:
1214 if ($Result !== NULL)
1216 $ReturnValue = $Result;
1221 case self::EVENTTYPE_NAMED:
1222 $CallbackName = is_array($Callback)
1223 ? (is_object($Callback[0])
1224 ? get_class($Callback[0])
1225 : $Callback[0]).
"::".$Callback[1]
1227 $ReturnValue[$CallbackName] = $Result;
1237 $this->
LogError(self::LOGLVL_WARNING,
1238 "Unregistered event signaled (".$EventName.
").");
1241 # return value if any to caller
1242 return $ReturnValue;
1252 return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
1257 # ---- Task Management ---------------------------------------------------
1283 $Priority = self::PRIORITY_MEDIUM, $Description =
"")
1285 # pack task info and write to database
1286 if ($Parameters === NULL) { $Parameters = array(); }
1287 $this->DB->Query(
"INSERT INTO TaskQueue"
1288 .
" (Callback, Parameters, Priority, Description)"
1289 .
" VALUES ('".addslashes(serialize($Callback)).
"', '"
1290 .addslashes(serialize($Parameters)).
"', ".intval($Priority).
", '"
1291 .addslashes($Description).
"')");
1312 $Priority = self::PRIORITY_MEDIUM, $Description =
"")
1316 $QueryResult = $this->DB->Query(
"SELECT TaskId,Priority FROM TaskQueue"
1317 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'"
1318 .($Parameters ?
" AND Parameters = '"
1319 .addslashes(serialize($Parameters)).
"'" :
""));
1320 if ($QueryResult !== FALSE)
1322 $Record = $this->DB->FetchRow();
1323 if ($Record[
"Priority"] > $Priority)
1325 $this->DB->Query(
"UPDATE TaskQueue"
1326 .
" SET Priority = ".intval($Priority)
1327 .
" WHERE TaskId = ".intval($Record[
"TaskId"]));
1334 $this->
QueueTask($Callback, $Parameters, $Priority, $Description);
1350 $FoundCount = $this->DB->Query(
"SELECT COUNT(*) AS FoundCount FROM TaskQueue"
1351 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'"
1352 .($Parameters ?
" AND Parameters = '"
1353 .addslashes(serialize($Parameters)).
"'" :
""),
1355 + $this->DB->Query(
"SELECT COUNT(*) AS FoundCount FROM RunningTasks"
1356 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'"
1357 .($Parameters ?
" AND Parameters = '"
1358 .addslashes(serialize($Parameters)).
"'" :
""),
1360 return ($FoundCount ? TRUE : FALSE);
1370 return $this->DB->Query(
"SELECT COUNT(*) AS QueueSize FROM TaskQueue"
1371 .($Priority ?
" WHERE Priority = ".intval($Priority) :
""),
1384 return $this->GetTaskList(
"SELECT * FROM TaskQueue"
1385 .
" ORDER BY Priority, TaskId ", $Count, $Offset);
1397 return $this->GetTaskList(
"SELECT * FROM RunningTasks"
1398 .
" WHERE StartedAt >= '".date(
"Y-m-d H:i:s",
1399 (time() - ini_get(
"max_execution_time"))).
"'"
1400 .
" ORDER BY StartedAt", $Count, $Offset);
1412 return $this->GetTaskList(
"SELECT * FROM RunningTasks"
1413 .
" WHERE StartedAt < '".date(
"Y-m-d H:i:s",
1414 (time() - ini_get(
"max_execution_time"))).
"'"
1415 .
" ORDER BY StartedAt", $Count, $Offset);
1424 return $this->DB->Query(
"SELECT COUNT(*) AS Count FROM RunningTasks"
1425 .
" WHERE StartedAt < '".date(
"Y-m-d H:i:s",
1426 (time() - ini_get(
"max_execution_time"))).
"'",
1437 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
1438 $this->DB->Query(
"INSERT INTO TaskQueue"
1439 .
" (Callback,Parameters,Priority,Description) "
1440 .
"SELECT Callback, Parameters, Priority, Description"
1441 .
" FROM RunningTasks WHERE TaskId = ".intval($TaskId));
1442 if ($NewPriority !== NULL)
1444 $NewTaskId = $this->DB->LastInsertId(
"TaskQueue");
1445 $this->DB->Query(
"UPDATE TaskQueue SET Priority = "
1446 .intval($NewPriority)
1447 .
" WHERE TaskId = ".intval($NewTaskId));
1449 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
1450 $this->DB->Query(
"UNLOCK TABLES");
1459 $this->DB->Query(
"DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
1460 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
1472 # assume task will not be found
1475 # look for task in task queue
1476 $this->DB->Query(
"SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
1478 # if task was not found in queue
1479 if (!$this->DB->NumRowsSelected())
1481 # look for task in running task list
1482 $this->DB->Query(
"SELECT * FROM RunningTasks WHERE TaskId = "
1487 if ($this->DB->NumRowsSelected())
1489 # if task was periodic
1490 $Row = $this->DB->FetchRow();
1491 if ($Row[
"Callback"] ==
1492 serialize(array(
"ApplicationFramework",
"PeriodicEventWrapper")))
1494 # unpack periodic task callback
1495 $WrappedCallback = unserialize($Row[
"Parameters"]);
1496 $Task[
"Callback"] = $WrappedCallback[1];
1497 $Task[
"Parameters"] = $WrappedCallback[2];
1501 # unpack task callback and parameters
1502 $Task[
"Callback"] = unserialize($Row[
"Callback"]);
1503 $Task[
"Parameters"] = unserialize($Row[
"Parameters"]);
1507 # return task to caller
1520 if ($NewValue !== NULL)
1522 $this->Settings[
"TaskExecutionEnabled"] = $NewValue ? 1 : 0;
1523 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
1524 .
" SET TaskExecutionEnabled = "
1525 .$this->Settings[
"TaskExecutionEnabled"]);
1527 return $this->Settings[
"TaskExecutionEnabled"];
1537 if (func_num_args() && ($NewValue >= 1))
1539 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
1540 .
" SET MaxTasksRunning = ".intval($NewValue));
1541 $this->Settings[
"MaxTasksRunning"] = intval($NewValue);
1543 return $this->Settings[
"MaxTasksRunning"];
1555 if (func_num_args() && !ini_get(
"safe_mode"))
1557 if ($NewValue != $this->Settings[
"MaxExecTime"])
1559 $this->Settings[
"MaxExecTime"] = max($NewValue, 5);
1560 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
1561 .
" SET MaxExecTime = '"
1562 .intval($this->Settings[
"MaxExecTime"]).
"'");
1564 ini_set(
"max_execution_time", $this->Settings[
"MaxExecTime"]);
1565 set_time_limit($this->Settings[
"MaxExecTime"]);
1567 return ini_get(
"max_execution_time");
1572 # ---- Backward Compatibility --------------------------------------------
1580 return $this->FindFile(
1581 $this->IncludeDirList, $BaseName, array(
"tpl",
"html"));
1587 # ---- PRIVATE INTERFACE -------------------------------------------------
1589 private $ActiveUI =
"default";
1590 private $BrowserDetectFunc;
1592 private $DefaultPage =
"Home";
1593 private $EnvIncludes = array();
1594 private $ExecutionStartTime;
1595 private $FoundUIFiles = array();
1596 private $AdditionalRequiredUIFiles = array();
1597 private $HtmlCharset =
"UTF-8";
1598 private $JumpToPage = NULL;
1600 private $MaxRunningTasksToTrack = 250;
1601 private $PostProcessingFuncs = array();
1602 private $RunningInBackground = FALSE;
1603 private $RunningTask;
1605 private $SuppressHTML = FALSE;
1606 private $SaveTemplateLocationCache = FALSE;
1607 private $UnbufferedCallbacks = array();
1609 private static $ObjectDirectories = array();
1610 private static $SessionLifetime = 1440;
1612 # set to TRUE to not close browser connection before running
1613 # background tasks (useful when debugging)
1614 private $NoTSR = FALSE;
1616 private $PeriodicEvents = array(
1617 "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
1618 "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
1619 "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
1620 "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
1621 "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
1623 private $UIEvents = array(
1624 "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
1625 "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
1626 "EVENT_PHP_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
1627 "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
1628 "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
1634 private function LoadSettings()
1636 # read settings in from database
1637 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
1638 $this->Settings = $this->DB->FetchRow();
1640 # if settings were not previously initialized
1641 if (!$this->Settings)
1643 # initialize settings in database
1644 $this->DB->Query(
"INSERT INTO ApplicationFrameworkSettings"
1645 .
" (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
1647 # read new settings in from database
1648 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
1649 $this->Settings = $this->DB->FetchRow();
1652 # if template location cache has been saved to database
1653 if (isset($this->Settings[
"TemplateLocationCache"]))
1655 # unserialize cache values into array and use if valid
1656 $Cache = unserialize($this->Settings[
"TemplateLocationCache"]);
1657 $this->Settings[
"TemplateLocationCache"] =
1658 count($Cache) ? $Cache : array();
1662 # start with empty cache
1663 $this->Settings[
"TemplateLocationCache"] = array();
1683 private function FindFile($DirectoryList, $BaseName,
1684 $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
1686 # generate template cache index for this page
1687 $CacheIndex = md5(serialize($DirectoryList))
1688 .
":".$this->ActiveUI.
":".$BaseName;
1690 # if we have cached location and cache expiration time has not elapsed
1691 if (($this->Settings[
"TemplateLocationCacheInterval"] > 0)
1692 && count($this->Settings[
"TemplateLocationCache"])
1693 && array_key_exists($CacheIndex,
1694 $this->Settings[
"TemplateLocationCache"])
1695 && (time() < strtotime(
1696 $this->Settings[
"TemplateLocationCacheExpiration"])))
1698 # use template location from cache
1699 $FoundFileName = $this->Settings[
1700 "TemplateLocationCache"][$CacheIndex];
1704 # if suffixes specified and base name does not include suffix
1705 if (count($PossibleSuffixes)
1706 && !preg_match(
"/\.[a-zA-Z0-9]+$/", $BaseName))
1708 # add versions of file names with suffixes to file name list
1709 $FileNames = array();
1710 foreach ($PossibleSuffixes as $Suffix)
1712 $FileNames[] = $BaseName.
".".$Suffix;
1717 # use base name as file name
1718 $FileNames = array($BaseName);
1721 # if prefixes specified
1722 if (count($PossiblePrefixes))
1724 # add versions of file names with prefixes to file name list
1725 $NewFileNames = array();
1726 foreach ($FileNames as $FileName)
1728 foreach ($PossiblePrefixes as $Prefix)
1730 $NewFileNames[] = $Prefix.$FileName;
1733 $FileNames = $NewFileNames;
1736 # for each possible location
1737 $FoundFileName = NULL;
1738 foreach ($DirectoryList as $Dir)
1740 # substitute active UI name into path
1741 $Dir = str_replace(
"%ACTIVEUI%", $this->ActiveUI, $Dir);
1743 # for each possible file name
1744 foreach ($FileNames as $File)
1746 # if template is found at location
1747 if (file_exists($Dir.$File))
1749 # save full template file name and stop looking
1750 $FoundFileName = $Dir.$File;
1756 # save location in cache
1757 $this->Settings[
"TemplateLocationCache"][$CacheIndex]
1760 # set flag indicating that cache should be saved
1761 $this->SaveTemplateLocationCache = TRUE;
1764 # return full template file name to caller
1765 return $FoundFileName;
1774 private function GetRequiredFilesNotYetLoaded($PageContentFile)
1776 # start out assuming no files required
1777 $RequiredFiles = array();
1779 # if page content file supplied
1780 if ($PageContentFile)
1782 # if file containing list of required files is available
1783 $Path = dirname($PageContentFile);
1784 $RequireListFile = $Path.
"/REQUIRES";
1785 if (file_exists($RequireListFile))
1787 # read in list of required files
1788 $RequestedFiles = file($RequireListFile);
1790 # for each line in required file list
1791 foreach ($RequestedFiles as $Line)
1793 # if line is not a comment
1794 $Line = trim($Line);
1795 if (!preg_match(
"/^#/", $Line))
1797 # if file has not already been loaded
1798 if (!in_array($Line, $this->FoundUIFiles))
1800 # add to list of required files
1801 $RequiredFiles[] = $Line;
1808 # add in additional required files if any
1809 if (count($this->AdditionalRequiredUIFiles))
1811 # make sure there are no duplicates
1812 $AdditionalRequiredUIFiles = array_unique(
1813 $this->AdditionalRequiredUIFiles);
1815 $RequiredFiles = array_merge(
1816 $RequiredFiles, $AdditionalRequiredUIFiles);
1819 # return list of required files to caller
1820 return $RequiredFiles;
1823 private function SetUpObjectAutoloading()
1825 function __autoload($ClassName)
1827 ApplicationFramework::AutoloadObjects($ClassName);
1832 static function AutoloadObjects($ClassName)
1834 foreach (self::$ObjectDirectories as $Location => $Prefix)
1836 $FileNames = scandir($Location);
1837 $TargetName = strtolower($Prefix.$ClassName.
".php");
1838 foreach ($FileNames as $FileName)
1840 if (strtolower($FileName) == $TargetName)
1842 require_once($Location.$FileName);
1850 private function LoadUIFunctions()
1853 "local/interface/%ACTIVEUI%/include",
1854 "interface/%ACTIVEUI%/include",
1855 "local/interface/default/include",
1856 "interface/default/include",
1858 foreach ($Dirs as $Dir)
1860 $Dir = str_replace(
"%ACTIVEUI%", $this->ActiveUI, $Dir);
1863 $FileNames = scandir($Dir);
1864 foreach ($FileNames as $FileName)
1866 if (preg_match(
"/^F-([A-Za-z_]+)\.php/", $FileName, $Matches)
1867 || preg_match(
"/^F-([A-Za-z_]+)\.html/", $FileName, $Matches))
1869 if (!function_exists($Matches[1]))
1871 include_once($Dir.
"/".$FileName);
1879 private function ProcessPeriodicEvent($EventName, $Callback)
1881 # retrieve last execution time for event if available
1882 $Signature = self::GetCallbackSignature($Callback);
1883 $LastRun = $this->DB->Query(
"SELECT LastRunAt FROM PeriodicEvents"
1884 .
" WHERE Signature = '".addslashes($Signature).
"'",
"LastRunAt");
1886 # determine whether enough time has passed for event to execute
1887 $EventPeriods = array(
1888 "EVENT_HOURLY" => 60*60,
1889 "EVENT_DAILY" => 60*60*24,
1890 "EVENT_WEEKLY" => 60*60*24*7,
1891 "EVENT_MONTHLY" => 60*60*24*30,
1892 "EVENT_PERIODIC" => 0,
1894 $ShouldExecute = (($LastRun === NULL)
1895 || (time() > (strtotime($LastRun) + $EventPeriods[$EventName])))
1898 # if event should run
1901 # add event to task queue
1902 $WrapperCallback = array(
"ApplicationFramework",
"PeriodicEventWrapper");
1903 $WrapperParameters = array(
1904 $EventName, $Callback, array(
"LastRunAt" => $LastRun));
1909 private static function PeriodicEventWrapper($EventName, $Callback, $Parameters)
1912 if (!isset($DB)) { $DB =
new Database(); }
1915 $ReturnVal = call_user_func_array($Callback, $Parameters);
1917 # if event is already in database
1918 $Signature = self::GetCallbackSignature($Callback);
1919 if ($DB->Query(
"SELECT COUNT(*) AS EventCount FROM PeriodicEvents"
1920 .
" WHERE Signature = '".addslashes($Signature).
"'",
"EventCount"))
1922 # update last run time for event
1923 $DB->Query(
"UPDATE PeriodicEvents SET LastRunAt = "
1924 .(($EventName ==
"EVENT_PERIODIC")
1925 ?
"'".date(
"Y-m-d H:i:s", time() + ($ReturnVal * 60)).
"'"
1927 .
" WHERE Signature = '".addslashes($Signature).
"'");
1931 # add last run time for event to database
1932 $DB->Query(
"INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES "
1933 .
"('".addslashes($Signature).
"', "
1934 .(($EventName ==
"EVENT_PERIODIC")
1935 ?
"'".date(
"Y-m-d H:i:s", time() + ($ReturnVal * 60)).
"'"
1940 private static function GetCallbackSignature($Callback)
1942 return !is_array($Callback) ? $Callback
1943 : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
1947 private function PrepForTSR()
1949 # if HTML has been output and it's time to launch another task
1950 # (only TSR if HTML has been output because otherwise browsers
1951 # may misbehave after connection is closed)
1952 if (($this->JumpToPage || !$this->SuppressHTML)
1953 && (time() > (strtotime($this->Settings[
"LastTaskRunAt"])
1954 + (ini_get(
"max_execution_time")
1955 / $this->Settings[
"MaxTasksRunning"]) + 5))
1957 && $this->Settings[
"TaskExecutionEnabled"])
1959 # begin buffering output for TSR
1962 # let caller know it is time to launch another task
1967 # let caller know it is not time to launch another task
1972 private function LaunchTSR()
1974 # set headers to close out connection to browser
1977 ignore_user_abort(TRUE);
1978 header(
"Connection: close");
1979 header(
"Content-Length: ".ob_get_length());
1982 # output buffered content
1986 # write out any outstanding data and end HTTP session
1987 session_write_close();
1989 # set flag indicating that we are now running in background
1990 $this->RunningInBackground = TRUE;
1992 # if there is still a task in the queue
1995 # turn on output buffering to (hopefully) record any crash output
1998 # lock tables and grab last task run time to double check
1999 $this->DB->Query(
"LOCK TABLES ApplicationFrameworkSettings WRITE");
2000 $this->LoadSettings();
2002 # if still time to launch another task
2003 if (time() > (strtotime($this->Settings[
"LastTaskRunAt"])
2004 + (ini_get(
"max_execution_time")
2005 / $this->Settings[
"MaxTasksRunning"]) + 5))
2007 # update the "last run" time and release tables
2008 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
2009 .
" SET LastTaskRunAt = '".date(
"Y-m-d H:i:s").
"'");
2010 $this->DB->Query(
"UNLOCK TABLES");
2012 # run tasks while there is a task in the queue and enough time left
2016 $this->RunNextTask();
2024 $this->DB->Query(
"UNLOCK TABLES");
2036 private function GetTaskList($DBQuery, $Count, $Offset)
2038 $this->DB->Query($DBQuery.
" LIMIT ".intval($Offset).
",".intval($Count));
2040 while ($Row = $this->DB->FetchRow())
2042 $Tasks[$Row[
"TaskId"]] = $Row;
2043 if ($Row[
"Callback"] ==
2044 serialize(array(
"ApplicationFramework",
"PeriodicEventWrapper")))
2046 $WrappedCallback = unserialize($Row[
"Parameters"]);
2047 $Tasks[$Row[
"TaskId"]][
"Callback"] = $WrappedCallback[1];
2048 $Tasks[$Row[
"TaskId"]][
"Parameters"] = NULL;
2052 $Tasks[$Row[
"TaskId"]][
"Callback"] = unserialize($Row[
"Callback"]);
2053 $Tasks[$Row[
"TaskId"]][
"Parameters"] = unserialize($Row[
"Parameters"]);
2062 private function RunNextTask()
2064 # look for task at head of queue
2065 $this->DB->Query(
"SELECT * FROM TaskQueue ORDER BY Priority, TaskId LIMIT 1");
2066 $Task = $this->DB->FetchRow();
2068 # if there was a task available
2071 # move task from queue to running tasks list
2072 $this->DB->Query(
"INSERT INTO RunningTasks "
2073 .
"(TaskId,Callback,Parameters,Priority,Description) "
2074 .
"SELECT * FROM TaskQueue WHERE TaskId = "
2075 .intval($Task[
"TaskId"]));
2076 $this->DB->Query(
"DELETE FROM TaskQueue WHERE TaskId = "
2077 .intval($Task[
"TaskId"]));
2079 # unpack stored task info
2080 $Callback = unserialize($Task[
"Callback"]);
2081 $Parameters = unserialize($Task[
"Parameters"]);
2083 # attempt to load task callback if not already available
2087 $this->RunningTask = $Task;
2090 call_user_func_array($Callback, $Parameters);
2094 call_user_func($Callback);
2096 unset($this->RunningTask);
2098 # remove task from running tasks list
2099 $this->DB->Query(
"DELETE FROM RunningTasks"
2100 .
" WHERE TaskId = ".intval($Task[
"TaskId"]));
2102 # prune running tasks list if necessary
2103 $RunningTasksCount = $this->DB->Query(
2104 "SELECT COUNT(*) AS TaskCount FROM RunningTasks",
"TaskCount");
2105 if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
2107 $this->DB->Query(
"DELETE FROM RunningTasks ORDER BY StartedAt"
2108 .
" LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
2120 if (isset($this->RunningTask))
2122 if (function_exists(
"error_get_last"))
2124 $CrashInfo[
"LastError"] = error_get_last();
2126 if (ob_get_length() !== FALSE)
2128 $CrashInfo[
"OutputBuffer"] = ob_get_contents();
2130 if (isset($CrashInfo))
2133 $DB->Query(
"UPDATE RunningTasks SET CrashInfo = '"
2134 .addslashes(serialize($CrashInfo))
2135 .
"' WHERE TaskId = ".intval($this->RunningTask[
"TaskId"]));
2142 if (ob_get_length() !== FALSE)
2145 <table width=
"100%" cellpadding=
"5" style=
"border: 2px solid #666666; background: #FFCCCC; font-family: Courier New, Courier, monospace; margin-top: 10px; font-weight: bold;"><tr><td>
2146 <div style=
"font-size: 200%;">CRASH OUTPUT</div><?
PHP
2148 ?></td></tr></table><?
PHP
2168 private function AddToDirList($DirList, $Dir, $SearchLast, $SkipSlashCheck)
2170 # convert incoming directory to array of directories (if needed)
2171 $Dirs = is_array($Dir) ? $Dir : array($Dir);
2173 # reverse array so directories are searched in specified order
2174 $Dirs = array_reverse($Dirs);
2176 # for each directory
2177 foreach ($Dirs as $Location)
2179 # make sure directory includes trailing slash
2180 if (!$SkipSlashCheck)
2182 $Location = $Location
2183 .((substr($Location, -1) !=
"/") ?
"/" :
"");
2186 # remove directory from list if already present
2187 if (in_array($Location, $DirList))
2189 $DirList = array_diff(
2190 $DirList, array($Location));
2193 # add directory to list of directories
2196 array_push($DirList, $Location);
2200 array_unshift($DirList, $Location);
2204 # return updated directory list to caller
2208 # default list of directories to search for user interface (HTML/TPL) files
2209 private $InterfaceDirList = array(
2210 "local/interface/%ACTIVEUI%/",
2211 "interface/%ACTIVEUI%/",
2212 "local/interface/default/",
2213 "interface/default/",
2215 # default list of directories to search for UI include (CSS, JavaScript,
2216 # common HTML, common PHP, /etc) files
2217 private $IncludeDirList = array(
2218 "local/interface/%ACTIVEUI%/include/",
2219 "interface/%ACTIVEUI%/include/",
2220 "local/interface/default/include/",
2221 "interface/default/include/",
2223 # default list of directories to search for image files
2224 private $ImageDirList = array(
2225 "local/interface/%ACTIVEUI%/images/",
2226 "interface/%ACTIVEUI%/images/",
2227 "local/interface/default/images/",
2228 "interface/default/images/",
2230 # default list of directories to search for files containing PHP functions
2231 private $FunctionDirList = array(
2232 "local/interface/%ACTIVEUI%/include/",
2233 "interface/%ACTIVEUI%/include/",
2234 "local/interface/default/include/",
2235 "interface/default/include/",
2240 const NOVALUE =
".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";