00001 <?PHP
00002
00007 class ApplicationFramework {
00008
00009 # ---- PUBLIC INTERFACE --------------------------------------------------
00010
00012
00020 function __construct($ObjectDirectories = NULL)
00021 {
00022 # save execution start time
00023 $this->ExecutionStartTime = microtime(TRUE);
00024
00025 # save object directory search list
00026 if ($ObjectDirectories) { $this->AddObjectDirectories($ObjectDirectories); }
00027
00028 # set up object file autoloader
00029 $this->SetUpObjectAutoloading();
00030
00031 # set up function to output any buffered text in case of crash
00032 register_shutdown_function(array($this, "OnCrash"));
00033
00034 # set up our internal environment
00035 $this->DB = new Database();
00036
00037 # load our settings from database
00038 $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
00039 $this->Settings = $this->DB->FetchRow();
00040 if (!$this->Settings)
00041 {
00042 $this->DB->Query("INSERT INTO ApplicationFrameworkSettings"
00043 ." (LastTaskRunAt) VALUES (NOW())");
00044 $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
00045 $this->Settings = $this->DB->FetchRow();
00046 }
00047
00048 # set PHP maximum execution time
00049 $this->MaxExecutionTime($this->Settings["MaxExecTime"]);
00050
00051 # register events we handle internally
00052 $this->RegisterEvent($this->PeriodicEvents);
00053 $this->RegisterEvent($this->UIEvents);
00054 }
00062 function AddObjectDirectories($Dirs)
00063 {
00064 foreach ($Dirs as $Location => $Prefix)
00065 {
00066 $Location = $Location
00067 .((substr($Location, -1) != "/") ? "/" : "");
00068 self::$ObjectDirectories = array_merge(
00069 array($Location => $Prefix),
00070 self::$ObjectDirectories);
00071 }
00072 }
00073
00078 function LoadPage($PageName)
00079 {
00080 # buffer any output from includes or PHP file
00081 ob_start();
00082
00083 # include any files needed to set up execution environment
00084 foreach ($this->EnvIncludes as $IncludeFile)
00085 {
00086 include($IncludeFile);
00087 }
00088
00089 # sanitize incoming page name
00090 $PageName = preg_replace("/[^a-zA-Z0-9_.-]/", "", $PageName);
00091
00092 # signal page load
00093 $this->SignalEvent("EVENT_PAGE_LOAD", array("PageName" => $PageName));
00094
00095 # signal PHP file load
00096 $SignalResult = $this->SignalEvent("EVENT_PHP_FILE_LOAD", array(
00097 "PageName" => $PageName));
00098
00099 # if signal handler returned new page name value
00100 $NewPageName = $PageName;
00101 if (($SignalResult["PageName"] != $PageName)
00102 && strlen($SignalResult["PageName"]))
00103 {
00104 # if new page name value is page file
00105 if (file_exists($SignalResult["PageName"]))
00106 {
00107 # use new value for PHP file name
00108 $PageFile = $SignalResult["PageName"];
00109 }
00110 else
00111 {
00112 # use new value for page name
00113 $NewPageName = $SignalResult["PageName"];
00114 }
00115 }
00116
00117 # if we do not already have a PHP file
00118 if (!isset($PageFile))
00119 {
00120 # look for PHP file for page
00121 $OurPageFile = "pages/".$NewPageName.".php";
00122 $LocalPageFile = "local/pages/".$NewPageName.".php";
00123 $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
00124 : (file_exists($OurPageFile) ? $OurPageFile
00125 : "pages/".$this->DefaultPage.".php");
00126 }
00127
00128 # load PHP file
00129 include($PageFile);
00130
00131 # save buffered output to be displayed later after HTML file loads
00132 $PageOutput = ob_get_contents();
00133 ob_end_clean();
00134
00135 # set up for possible TSR (Terminate and Stay Resident :))
00136 $ShouldTSR = $this->PrepForTSR();
00137
00138 # if PHP file indicated we should autorefresh to somewhere else
00139 if ($this->JumpToPage)
00140 {
00141 if (!strlen(trim($PageOutput)))
00142 {
00143 ?><html>
00144 <head>
00145 <meta http-equiv="refresh" content="0; URL=<?PHP
00146 print($this->JumpToPage); ?>">
00147 </head>
00148 <body bgcolor="white">
00149 </body>
00150 </html><?PHP
00151 }
00152 }
00153 # else if HTML loading is not suppressed
00154 elseif (!$this->SuppressHTML)
00155 {
00156 # set content-type to get rid of diacritic errors
00157 header("Content-Type: text/html; charset="
00158 .$this->HtmlCharset, TRUE);
00159
00160 # load common HTML file if available
00161 $HtmlFile = $this->FindCommonTemplate("Common");
00162 if ($HtmlFile) { include($HtmlFile); }
00163
00164 # load UI functions
00165 $this->LoadUIFunctions();
00166
00167 # begin buffering content
00168 ob_start();
00169
00170 # signal HTML file load
00171 $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD", array(
00172 "PageName" => $PageName));
00173
00174 # if signal handler returned new page name value
00175 $NewPageName = $PageName;
00176 $HtmlFile = NULL;
00177 if (($SignalResult["PageName"] != $PageName)
00178 && strlen($SignalResult["PageName"]))
00179 {
00180 # if new page name value is HTML file
00181 if (file_exists($SignalResult["PageName"]))
00182 {
00183 # use new value for HTML file name
00184 $HtmlFile = $SignalResult["PageName"];
00185 }
00186 else
00187 {
00188 # use new value for page name
00189 $NewPageName = $SignalResult["PageName"];
00190 }
00191 }
00192
00193 # load page content HTML file if available
00194 if ($HtmlFile === NULL)
00195 {
00196 $HtmlFile = $this->FindTemplate($this->ContentTemplateList, $NewPageName);
00197 }
00198 if ($HtmlFile)
00199 {
00200 include($HtmlFile);
00201 }
00202 else
00203 {
00204 print("<h2>ERROR: No HTML/TPL template found"
00205 ." for this page.</h2>");
00206 }
00207
00208 # signal HTML file load complete
00209 $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD_COMPLETE");
00210
00211 # stop buffering and save content
00212 $BufferedContent = ob_get_contents();
00213 ob_end_clean();
00214
00215 # load page start HTML file if available
00216 $HtmlFile = $this->FindCommonTemplate("Start");
00217 if ($HtmlFile) { include($HtmlFile); }
00218
00219 # write out page content
00220 print($BufferedContent);
00221
00222 # load page end HTML file if available
00223 $HtmlFile = $this->FindCommonTemplate("End");
00224 if ($HtmlFile) { include($HtmlFile); }
00225 }
00226
00227 # run any post-processing routines
00228 foreach ($this->PostProcessingFuncs as $Func)
00229 {
00230 call_user_func_array($Func["FunctionName"], $Func["Arguments"]);
00231 }
00232
00233 # write out any output buffered from page code execution
00234 if (strlen($PageOutput))
00235 {
00236 if (!$this->SuppressHTML)
00237 {
00238 ?><table width="100%" cellpadding="5"
00239 style="border: 2px solid #666666; background: #CCCCCC;
00240 font-family: Courier New, Courier, monospace;
00241 margin-top: 10px;"><tr><td><?PHP
00242 }
00243 if ($this->JumpToPage)
00244 {
00245 ?><div style="color: #666666;"><span style="font-size: 150%;">
00246 <b>Page Jump Aborted</b></span>
00247 (because of error or other unexpected output)<br />
00248 <b>Jump Target:</b>
00249 <i><?PHP print($this->JumpToPage); ?></i></div><?PHP
00250 }
00251 print($PageOutput);
00252 if (!$this->SuppressHTML)
00253 {
00254 ?></td></tr></table><?PHP
00255 }
00256 }
00257
00258 # terminate and stay resident (TSR!) if indicated and HTML has been output
00259 # (only TSR if HTML has been output because otherwise browsers will misbehave)
00260 if ($ShouldTSR) { $this->LaunchTSR(); }
00261 }
00262
00269 function SetJumpToPage($Page)
00270 {
00271 if ((strpos($Page, "?") === FALSE)
00272 && ((strpos($Page, "=") !== FALSE)
00273 || ((strpos($Page, ".php") === FALSE)
00274 && (strpos($Page, ".htm") === FALSE)
00275 && (strpos($Page, "/") === FALSE))))
00276 {
00277 $this->JumpToPage = "index.php?P=".$Page;
00278 }
00279 else
00280 {
00281 $this->JumpToPage = $Page;
00282 }
00283 }
00284
00289 function JumpToPageIsSet()
00290 {
00291 return ($this->JumpToPage === NULL) ? FALSE : TRUE;
00292 }
00293
00303 function HtmlCharset($NewSetting = NULL)
00304 {
00305 if ($NewSetting !== NULL) { $this->HtmlCharset = $NewSetting; }
00306 return $this->HtmlCharset;
00307 }
00308
00315 function SuppressHTMLOutput($NewSetting = TRUE)
00316 {
00317 $this->SuppressHTML = $NewSetting;
00318 }
00319
00326 function ActiveUserInterface($UIName = NULL)
00327 {
00328 if ($UIName !== NULL)
00329 {
00330 $this->ActiveUI = preg_replace("/^SPTUI--/", "", $UIName);
00331 }
00332 return $this->ActiveUI;
00333 }
00334
00350 function AddPostProcessingCall($FunctionName,
00351 &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
00352 &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
00353 &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
00354 {
00355 $FuncIndex = count($this->PostProcessingFuncs);
00356 $this->PostProcessingFuncs[$FuncIndex]["FunctionName"] = $FunctionName;
00357 $this->PostProcessingFuncs[$FuncIndex]["Arguments"] = array();
00358 $Index = 1;
00359 while (isset(${"Arg".$Index}) && (${"Arg".$Index} !== self::NOVALUE))
00360 {
00361 $this->PostProcessingFuncs[$FuncIndex]["Arguments"][$Index]
00362 =& ${"Arg".$Index};
00363 $Index++;
00364 }
00365 }
00366
00372 function AddEnvInclude($FileName)
00373 {
00374 $this->EnvIncludes[] = $FileName;
00375 }
00376
00382 function GUIFile($FileName)
00383 {
00384 # pull off file name suffix
00385 $NamePieces = explode(".", $FileName);
00386 $Suffix = strtolower(array_pop($NamePieces));
00387
00388 # determine which location to search based on file suffix
00389 $ImageSuffixes = array("gif", "jpg", "png");
00390 $FileList = in_array($Suffix, $ImageSuffixes)
00391 ? $this->ImageFileList : $this->CommonTemplateList;
00392
00393 # search for file and return result to caller
00394 return $this->FindTemplate($FileList, $FileName);
00395 }
00396
00406 function PUIFile($FileName)
00407 {
00408 $FullFileName = $this->GUIFile($FileName);
00409 if ($FullFileName) { print($FullFileName); }
00410 }
00411
00416 function FindCommonTemplate($PageName)
00417 {
00418 return $this->FindTemplate(
00419 array_merge($this->CommonTemplateList, $this->ContentTemplateList),
00420 $PageName);
00421 }
00422
00431 function LoadFunction($Callback)
00432 {
00433 if (!is_callable($Callback) && is_string($Callback))
00434 {
00435 $Locations = $this->FunctionFileList;
00436 foreach (self::$ObjectDirectories as $Location => $Prefix)
00437 {
00438 $Locations[] = $Location."%PAGENAME%.php";
00439 $Locations[] = $Location."%PAGENAME%.html";
00440 }
00441 $FunctionFileName = $this->FindTemplate($Locations, "F-".$Callback);
00442 if ($FunctionFileName)
00443 {
00444 include_once($FunctionFileName);
00445 }
00446 }
00447 return is_callable($Callback);
00448 }
00449
00454 function GetElapsedExecutionTime()
00455 {
00456 return microtime(TRUE) - $this->ExecutionStartTime;
00457 }
00458
00463 function GetSecondsBeforeTimeout()
00464 {
00465 return ini_get("max_execution_time") - $this->GetElapsedExecutionTime();
00466 }
00467
00468
00469
00470 # ---- Event Handling ----------------------------------------------------
00471
00473
00477 const EVENTTYPE_DEFAULT = 1;
00483 const EVENTTYPE_CHAIN = 2;
00489 const EVENTTYPE_FIRST = 3;
00497 const EVENTTYPE_NAMED = 4;
00498
00500 const ORDER_FIRST = 1;
00502 const ORDER_MIDDLE = 2;
00504 const ORDER_LAST = 3;
00505
00514 function RegisterEvent($EventsOrEventName, $EventType = NULL)
00515 {
00516 # convert parameters to array if not already in that form
00517 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
00518 : array($EventsOrEventName => $Type);
00519
00520 # for each event
00521 foreach ($Events as $Name => $Type)
00522 {
00523 # store event information
00524 $this->RegisteredEvents[$Name]["Type"] = $Type;
00525 $this->RegisteredEvents[$Name]["Hooks"] = array();
00526 }
00527 }
00528
00542 function HookEvent($EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
00543 {
00544 # convert parameters to array if not already in that form
00545 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
00546 : array($EventsOrEventName => $Callback);
00547
00548 # for each event
00549 $Success = TRUE;
00550 foreach ($Events as $EventName => $EventCallback)
00551 {
00552 # if callback is valid
00553 if (is_callable($EventCallback))
00554 {
00555 # if this is a periodic event we process internally
00556 if (isset($this->PeriodicEvents[$EventName]))
00557 {
00558 # process event now
00559 $this->ProcessPeriodicEvent($EventName, $EventCallback);
00560 }
00561 # if specified event has been registered
00562 elseif (isset($this->RegisteredEvents[$EventName]))
00563 {
00564 # add callback for event
00565 $this->RegisteredEvents[$EventName]["Hooks"][]
00566 = array("Callback" => $EventCallback, "Order" => $Order);
00567
00568 # sort callbacks by order
00569 if (count($this->RegisteredEvents[$EventName]["Hooks"]) > 1)
00570 {
00571 usort($this->RegisteredEvents[$EventName]["Hooks"],
00572 array("ApplicationFramework", "HookEvent_OrderCompare"));
00573 }
00574 }
00575 else
00576 {
00577 $Success = FALSE;
00578 }
00579 }
00580 else
00581 {
00582 $Success = FALSE;
00583 }
00584 }
00585
00586 # report to caller whether all callbacks were hooked
00587 return $Success;
00588 }
00589 private static function HookEvent_OrderCompare($A, $B)
00590 {
00591 if ($A["Order"] == $B["Order"]) { return 0; }
00592 return ($A["Order"] < $B["Order"]) ? -1 : 1;
00593 }
00594
00603 function SignalEvent($EventName, $Parameters = NULL)
00604 {
00605 $ReturnValue = NULL;
00606
00607 # if event has been registered
00608 if (isset($this->RegisteredEvents[$EventName]))
00609 {
00610 # set up default return value (if not NULL)
00611 switch ($this->RegisteredEvents[$EventName]["Type"])
00612 {
00613 case self::EVENTTYPE_CHAIN:
00614 $ReturnValue = $Parameters;
00615 break;
00616
00617 case self::EVENTTYPE_NAMED:
00618 $ReturnValue = array();
00619 break;
00620 }
00621
00622 # for each callback for this event
00623 foreach ($this->RegisteredEvents[$EventName]["Hooks"] as $Hook)
00624 {
00625 # invoke callback
00626 $Callback = $Hook["Callback"];
00627 $Result = ($Parameters !== NULL)
00628 ? call_user_func_array($Callback, $Parameters)
00629 : call_user_func($Callback);
00630
00631 # process return value based on event type
00632 switch ($this->RegisteredEvents[$EventName]["Type"])
00633 {
00634 case self::EVENTTYPE_CHAIN:
00635 $ReturnValue = $Result;
00636 $Parameters = $Result;
00637 break;
00638
00639 case self::EVENTTYPE_FIRST:
00640 if ($Result !== NULL)
00641 {
00642 $ReturnValue = $Result;
00643 break 2;
00644 }
00645 break;
00646
00647 case self::EVENTTYPE_NAMED:
00648 $CallbackName = is_array($Callback)
00649 ? (is_object($Callback[0])
00650 ? get_class($Callback[0])
00651 : $Callback[0])."::".$Callback[1]
00652 : $Callback;
00653 $ReturnValue[$CallbackName] = $Result;
00654 break;
00655
00656 default:
00657 break;
00658 }
00659 }
00660 }
00661
00662 # return value if any to caller
00663 return $ReturnValue;
00664 }
00665
00671 function IsStaticOnlyEvent($EventName)
00672 {
00673 return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
00674 }
00675
00676
00677
00678 # ---- Task Management ---------------------------------------------------
00679
00681
00683 const PRIORITY_HIGH = 1;
00685 const PRIORITY_MEDIUM = 2;
00687 const PRIORITY_LOW = 3;
00689 const PRIORITY_BACKGROUND = 4;
00690
00703 function QueueTask($Callback, $Parameters = NULL,
00704 $Priority = self::PRIORITY_MEDIUM, $Description = "")
00705 {
00706 # pack task info and write to database
00707 if ($Parameters === NULL) { $Parameters = array(); }
00708 $this->DB->Query("INSERT INTO TaskQueue"
00709 ." (Callback, Parameters, Priority, Description)"
00710 ." VALUES ('".addslashes(serialize($Callback))."', '"
00711 .addslashes(serialize($Parameters))."', ".intval($Priority).", '"
00712 .addslashes($Description)."')");
00713 }
00714
00732 function QueueUniqueTask($Callback, $Parameters = NULL,
00733 $Priority = self::PRIORITY_MEDIUM, $Description = "")
00734 {
00735 if ($this->TaskIsInQueue($Callback, $Parameters))
00736 {
00737 $QueryResult = $this->DB->Query("SELECT TaskId,Priority FROM TaskQueue"
00738 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
00739 .($Parameters ? " AND Parameters = '"
00740 .addslashes(serialize($Parameters))."'" : ""));
00741 if ($QueryResult !== FALSE)
00742 {
00743 $Record = $this->DB->FetchRow();
00744 if ($Record["Priority"] > $Priority)
00745 {
00746 $this->DB->Query("UPDATE TaskQueue"
00747 ." SET Priority = ".intval($Priority)
00748 ." WHERE TaskId = ".intval($Record["TaskId"]));
00749 }
00750 }
00751 return FALSE;
00752 }
00753 else
00754 {
00755 $this->QueueTask($Callback, $Parameters, $Priority, $Description);
00756 return TRUE;
00757 }
00758 }
00759
00769 function TaskIsInQueue($Callback, $Parameters = NULL)
00770 {
00771 $FoundCount = $this->DB->Query("SELECT COUNT(*) AS FoundCount FROM TaskQueue"
00772 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
00773 .($Parameters ? " AND Parameters = '"
00774 .addslashes(serialize($Parameters))."'" : ""),
00775 "FoundCount")
00776 + $this->DB->Query("SELECT COUNT(*) AS FoundCount FROM RunningTasks"
00777 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
00778 .($Parameters ? " AND Parameters = '"
00779 .addslashes(serialize($Parameters))."'" : ""),
00780 "FoundCount");
00781 return ($FoundCount ? TRUE : FALSE);
00782 }
00783
00789 function GetTaskQueueSize($Priority = NULL)
00790 {
00791 return $this->DB->Query("SELECT COUNT(*) AS QueueSize FROM TaskQueue"
00792 .($Priority ? " WHERE Priority = ".intval($Priority) : ""),
00793 "QueueSize");
00794 }
00795
00803 function GetQueuedTaskList($Count = 100, $Offset = 0)
00804 {
00805 return $this->GetTaskList("SELECT * FROM TaskQueue"
00806 ." ORDER BY Priority, TaskId ", $Count, $Offset);
00807 }
00808
00816 function GetRunningTaskList($Count = 100, $Offset = 0)
00817 {
00818 return $this->GetTaskList("SELECT * FROM RunningTasks"
00819 ." WHERE StartedAt >= '".date("Y-m-d H:i:s",
00820 (time() - ini_get("max_execution_time")))."'"
00821 ." ORDER BY StartedAt", $Count, $Offset);
00822 }
00823
00831 function GetOrphanedTaskList($Count = 100, $Offset = 0)
00832 {
00833 return $this->GetTaskList("SELECT * FROM RunningTasks"
00834 ." WHERE StartedAt < '".date("Y-m-d H:i:s",
00835 (time() - ini_get("max_execution_time")))."'"
00836 ." ORDER BY StartedAt", $Count, $Offset);
00837 }
00838
00843 function ReQueueOrphanedTask($TaskId)
00844 {
00845 $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
00846 $this->DB->Query("INSERT INTO TaskQueue"
00847 ." (Callback,Parameters,Priority,Description) "
00848 ."SELECT Callback, Parameters, Priority, Description"
00849 ." FROM RunningTasks WHERE TaskId = ".intval($TaskId));
00850 $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
00851 $this->DB->Query("UNLOCK TABLES");
00852 }
00853
00858 function DeleteOrphanedTask($TaskId)
00859 {
00860 $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
00861 }
00862
00868 function MaxTasks($NewValue = NULL)
00869 {
00870 if (func_num_args() && ($NewValue >= 1))
00871 {
00872 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
00873 ." SET MaxTasksRunning = '".intval($NewValue)."'");
00874 $this->Settings["MaxTasksRunning"] = intval($NewValue);
00875 }
00876 return $this->Settings["MaxTasksRunning"];
00877 }
00878
00886 function MaxExecutionTime($NewValue = NULL)
00887 {
00888 if (func_num_args())
00889 {
00890 if ($NewValue != $this->Settings["MaxExecTime"])
00891 {
00892 $this->Settings["MaxExecTime"] = max($NewValue, 5);
00893 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
00894 ." SET MaxExecTime = '"
00895 .intval($this->Settings["MaxExecTime"])."'");
00896 }
00897 ini_set("max_execution_time", $this->Settings["MaxExecTime"]);
00898 set_time_limit($this->Settings["MaxExecTime"]);
00899 }
00900 return ini_get("max_execution_time");
00901 }
00902
00903
00904
00905 # ---- PRIVATE INTERFACE -------------------------------------------------
00906
00907 private $JumpToPage = NULL;
00908 private $SuppressHTML = FALSE;
00909 private $DefaultPage = "Home";
00910 private $ActiveUI = "default";
00911 private $HtmlCharset = "UTF-8";
00912 private $PostProcessingFuncs = array();
00913 private $EnvIncludes = array();
00914 private $DB;
00915 private $Settings;
00916 private $ExecutionStartTime;
00917 private static $ObjectDirectories = array();
00918 private $MaxRunningTasksToTrack = 250;
00919 private $RunningTask;
00920
00921 private $PeriodicEvents = array(
00922 "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
00923 "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
00924 "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
00925 "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
00926 "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
00927 );
00928 private $UIEvents = array(
00929 "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
00930 "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
00931 "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
00932 "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
00933 );
00934
00935 private function FindTemplate($FileList, $PageName)
00936 {
00937 $FileNameFound = NULL;
00938 foreach ($FileList as $FileName)
00939 {
00940 $FileName = str_replace("%ACTIVEUI%", $this->ActiveUI, $FileName);
00941 $FileName = str_replace("%PAGENAME%", $PageName, $FileName);
00942 if (file_exists($FileName))
00943 {
00944 $FileNameFound = $FileName;
00945 break;
00946 }
00947 }
00948 return $FileNameFound;
00949 }
00950
00951 private function SetUpObjectAutoloading()
00952 {
00953 function __autoload($ClassName)
00954 {
00955 ApplicationFramework::AutoloadObjects($ClassName);
00956 }
00957 }
00958
00960 static function AutoloadObjects($ClassName)
00961 {
00962 foreach (self::$ObjectDirectories as $Location => $Prefix)
00963 {
00964 $FileName = $Location.$Prefix.$ClassName.".php";
00965 if (file_exists($FileName))
00966 {
00967 require_once($FileName);
00968 break;
00969 }
00970 }
00971 }
00974 private function LoadUIFunctions()
00975 {
00976 $Dirs = array(
00977 "local/interface/%ACTIVEUI%/include",
00978 "interface/%ACTIVEUI%/include",
00979 "local/interface/default/include",
00980 "interface/default/include",
00981 );
00982 foreach ($Dirs as $Dir)
00983 {
00984 $Dir = str_replace("%ACTIVEUI%", $this->ActiveUI, $Dir);
00985 if (is_dir($Dir))
00986 {
00987 $FileNames = scandir($Dir);
00988 foreach ($FileNames as $FileName)
00989 {
00990 if (preg_match("/^F-([A-Za-z_]+)\.php/", $FileName, $Matches)
00991 || preg_match("/^F-([A-Za-z_]+)\.html/", $FileName, $Matches))
00992 {
00993 if (!function_exists($Matches[1]))
00994 {
00995 include_once($Dir."/".$FileName);
00996 }
00997 }
00998 }
00999 }
01000 }
01001 }
01002
01003 private function ProcessPeriodicEvent($EventName, $Callback)
01004 {
01005 # retrieve last execution time for event if available
01006 $Signature = self::GetCallbackSignature($Callback);
01007 $LastRun = $this->DB->Query("SELECT LastRunAt FROM PeriodicEvents"
01008 ." WHERE Signature = '".addslashes($Signature)."'", "LastRunAt");
01009
01010 # determine whether enough time has passed for event to execute
01011 $EventPeriods = array(
01012 "EVENT_HOURLY" => 60*60,
01013 "EVENT_DAILY" => 60*60*24,
01014 "EVENT_WEEKLY" => 60*60*24*7,
01015 "EVENT_MONTHLY" => 60*60*24*30,
01016 "EVENT_PERIODIC" => 0,
01017 );
01018 $ShouldExecute = (($LastRun === NULL)
01019 || (time() > (strtotime($LastRun) + $EventPeriods[$EventName])))
01020 ? TRUE : FALSE;
01021
01022 # if event should run
01023 if ($ShouldExecute)
01024 {
01025 # add event to task queue
01026 $WrapperCallback = array("ApplicationFramework", "PeriodicEventWrapper");
01027 $WrapperParameters = array(
01028 $EventName, $Callback, array("LastRunAt" => $LastRun));
01029 $this->QueueUniqueTask($WrapperCallback, $WrapperParameters);
01030 }
01031 }
01032
01033 private static function PeriodicEventWrapper($EventName, $Callback, $Parameters)
01034 {
01035 static $DB;
01036 if (!isset($DB)) { $DB = new Database(); }
01037
01038 # run event
01039 $ReturnVal = call_user_func_array($Callback, $Parameters);
01040
01041 # if event is already in database
01042 $Signature = self::GetCallbackSignature($Callback);
01043 if ($DB->Query("SELECT COUNT(*) AS EventCount FROM PeriodicEvents"
01044 ." WHERE Signature = '".addslashes($Signature)."'", "EventCount"))
01045 {
01046 # update last run time for event
01047 $DB->Query("UPDATE PeriodicEvents SET LastRunAt = "
01048 .(($EventName == "EVENT_PERIODIC")
01049 ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
01050 : "NOW()")
01051 ." WHERE Signature = '".addslashes($Signature)."'");
01052 }
01053 else
01054 {
01055 # add last run time for event to database
01056 $DB->Query("INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES "
01057 ."('".addslashes($Signature)."', "
01058 .(($EventName == "EVENT_PERIODIC")
01059 ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
01060 : "NOW()").")");
01061 }
01062 }
01063
01064 private static function GetCallbackSignature($Callback)
01065 {
01066 return !is_array($Callback) ? $Callback
01067 : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
01068 ."::".$Callback[1];
01069 }
01070
01071 private function PrepForTSR()
01072 {
01073 # if HTML has been output and it's time to launch another task
01074 # (only TSR if HTML has been output because otherwise browsers
01075 # may misbehave after connection is closed)
01076 if (($this->JumpToPage || !$this->SuppressHTML)
01077 && (time() > (strtotime($this->Settings["LastTaskRunAt"])
01078 + (ini_get("max_execution_time")
01079 / $this->Settings["MaxTasksRunning"]) + 5))
01080 && $this->GetTaskQueueSize())
01081 {
01082 # begin buffering output for TSR
01083 ob_start();
01084
01085 # let caller know it is time to launch another task
01086 return TRUE;
01087 }
01088 else
01089 {
01090 # let caller know it is not time to launch another task
01091 return FALSE;
01092 }
01093 }
01094
01095 private function LaunchTSR()
01096 {
01097 # set needed headers and
01098 ignore_user_abort(TRUE);
01099 header("Connection: close");
01100 header("Content-Length: ".ob_get_length());
01101
01102 # output buffered content
01103 ob_end_flush();
01104 flush();
01105
01106 # write out any outstanding data and end HTTP session
01107 session_write_close();
01108
01109 # if there is still a task in the queue
01110 if ($this->GetTaskQueueSize())
01111 {
01112 # turn on output buffering to (hopefully) record any crash output
01113 ob_start();
01114
01115 # lock tables and grab last task run time to double check
01116 $this->DB->Query("LOCK TABLES ApplicationFrameworkSettings WRITE");
01117 $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
01118 $this->Settings = $this->DB->FetchRow();
01119
01120 # if still time to launch another task
01121 if (time() > (strtotime($this->Settings["LastTaskRunAt"])
01122 + (ini_get("max_execution_time")
01123 / $this->Settings["MaxTasksRunning"]) + 5))
01124 {
01125 # update the "last run" time and release tables
01126 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
01127 ." SET LastTaskRunAt = '".date("Y-m-d H:i:s")."'");
01128 $this->DB->Query("UNLOCK TABLES");
01129
01130 # run tasks while there is a task in the queue and enough time left
01131 do
01132 {
01133 # run the next task
01134 $this->RunNextTask();
01135 }
01136 while ($this->GetTaskQueueSize()
01137 && ($this->GetSecondsBeforeTimeout() > 65));
01138 }
01139 else
01140 {
01141 # release tables
01142 $this->DB->Query("UNLOCK TABLES");
01143 }
01144 }
01145 }
01146
01154 private function GetTaskList($DBQuery, $Count, $Offset)
01155 {
01156 $this->DB->Query($DBQuery." LIMIT ".intval($Offset).",".intval($Count));
01157 $Tasks = array();
01158 while ($Row = $this->DB->FetchRow())
01159 {
01160 $Tasks[$Row["TaskId"]] = $Row;
01161 if ($Row["Callback"] ==
01162 serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
01163 {
01164 $WrappedCallback = unserialize($Row["Parameters"]);
01165 $Tasks[$Row["TaskId"]]["Callback"] = $WrappedCallback[1];
01166 $Tasks[$Row["TaskId"]]["Parameters"] = NULL;
01167 }
01168 else
01169 {
01170 $Tasks[$Row["TaskId"]]["Callback"] = unserialize($Row["Callback"]);
01171 $Tasks[$Row["TaskId"]]["Parameters"] = unserialize($Row["Parameters"]);
01172 }
01173 }
01174 return $Tasks;
01175 }
01176
01180 private function RunNextTask()
01181 {
01182 # look for task at head of queue
01183 $this->DB->Query("SELECT * FROM TaskQueue ORDER BY Priority, TaskId LIMIT 1");
01184 $Task = $this->DB->FetchRow();
01185
01186 # if there was a task available
01187 if ($Task)
01188 {
01189 # move task from queue to running tasks list
01190 $this->DB->Query("INSERT INTO RunningTasks "
01191 ."(TaskId,Callback,Parameters,Priority,Description) "
01192 ."SELECT * FROM TaskQueue WHERE TaskId = "
01193 .intval($Task["TaskId"]));
01194 $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = "
01195 .intval($Task["TaskId"]));
01196
01197 # unpack stored task info
01198 $Callback = unserialize($Task["Callback"]);
01199 $Parameters = unserialize($Task["Parameters"]);
01200
01201 # attempt to load task callback if not already available
01202 $this->LoadFunction($Callback);
01203
01204 # run task
01205 $this->RunningTask = $Task;
01206 call_user_func_array($Callback, $Parameters);
01207 unset($this->RunningTask);
01208
01209 # remove task from running tasks list
01210 $this->DB->Query("DELETE FROM RunningTasks"
01211 ." WHERE TaskId = ".intval($Task["TaskId"]));
01212
01213 # prune running tasks list if necessary
01214 $RunningTasksCount = $this->DB->Query(
01215 "SELECT COUNT(*) AS TaskCount FROM RunningTasks", "TaskCount");
01216 if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
01217 {
01218 $this->DB->Query("DELETE FROM RunningTasks ORDER BY StartedAt"
01219 ." LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
01220 }
01221 }
01222 }
01223
01229 function OnCrash()
01230 {
01231 if (isset($this->RunningTask))
01232 {
01233 if (function_exists("error_get_last"))
01234 {
01235 $CrashInfo["LastError"] = error_get_last();
01236 }
01237 if (ob_get_length() !== FALSE)
01238 {
01239 $CrashInfo["OutputBuffer"] = ob_get_contents();
01240 }
01241 if (isset($CrashInfo))
01242 {
01243 $DB = new Database();
01244 $DB->Query("UPDATE RunningTasks SET CrashInfo = '"
01245 .addslashes(serialize($CrashInfo))
01246 ."' WHERE TaskId = ".intval($this->RunningTask["TaskId"]));
01247 }
01248 }
01249
01250 print("\n");
01251 return;
01252
01253 if (ob_get_length() !== FALSE)
01254 {
01255 ?>
01256 <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>
01257 <div style="font-size: 200%;">CRASH OUTPUT</div><?PHP
01258 ob_end_flush();
01259 ?></td></tr></table><?PHP
01260 }
01261 }
01262
01263 private $CommonTemplateList = array(
01264 "local/interface/%ACTIVEUI%/include/StdPage%PAGENAME%.tpl",
01265 "local/interface/%ACTIVEUI%/include/StdPage%PAGENAME%.html",
01266 "local/interface/%ACTIVEUI%/include/%PAGENAME%.tpl",
01267 "local/interface/%ACTIVEUI%/include/%PAGENAME%.html",
01268 "local/interface/%ACTIVEUI%/include/SPT--StandardPage%PAGENAME%.tpl",
01269 "local/interface/%ACTIVEUI%/include/SPT--StandardPage%PAGENAME%.html",
01270 "local/interface/%ACTIVEUI%/include/SPT--%PAGENAME%.tpl",
01271 "local/interface/%ACTIVEUI%/include/SPT--%PAGENAME%.html",
01272 "local/interface/%ACTIVEUI%/include/%PAGENAME%",
01273 "interface/%ACTIVEUI%/include/StdPage%PAGENAME%.tpl",
01274 "interface/%ACTIVEUI%/include/StdPage%PAGENAME%.html",
01275 "interface/%ACTIVEUI%/include/%PAGENAME%.tpl",
01276 "interface/%ACTIVEUI%/include/%PAGENAME%.html",
01277 "interface/%ACTIVEUI%/include/SPT--StandardPage%PAGENAME%.tpl",
01278 "interface/%ACTIVEUI%/include/SPT--StandardPage%PAGENAME%.html",
01279 "interface/%ACTIVEUI%/include/SPT--%PAGENAME%.tpl",
01280 "interface/%ACTIVEUI%/include/SPT--%PAGENAME%.html",
01281 "interface/%ACTIVEUI%/include/%PAGENAME%",
01282 "SPTUI--%ACTIVEUI%/include/StdPage%PAGENAME%.tpl",
01283 "SPTUI--%ACTIVEUI%/include/StdPage%PAGENAME%.html",
01284 "SPTUI--%ACTIVEUI%/include/%PAGENAME%.tpl",
01285 "SPTUI--%ACTIVEUI%/include/%PAGENAME%.html",
01286 "SPTUI--%ACTIVEUI%/include/SPT--StandardPage%PAGENAME%.tpl",
01287 "SPTUI--%ACTIVEUI%/include/SPT--StandardPage%PAGENAME%.html",
01288 "SPTUI--%ACTIVEUI%/include/SPT--%PAGENAME%.tpl",
01289 "SPTUI--%ACTIVEUI%/include/SPT--%PAGENAME%.html",
01290 "SPTUI--%ACTIVEUI%/include/%PAGENAME%",
01291 "%ACTIVEUI%/include/StdPage%PAGENAME%.tpl",
01292 "%ACTIVEUI%/include/StdPage%PAGENAME%.html",
01293 "%ACTIVEUI%/include/%PAGENAME%.tpl",
01294 "%ACTIVEUI%/include/%PAGENAME%.html",
01295 "%ACTIVEUI%/include/SPT--StandardPage%PAGENAME%.tpl",
01296 "%ACTIVEUI%/include/SPT--StandardPage%PAGENAME%.html",
01297 "%ACTIVEUI%/include/SPT--%PAGENAME%.tpl",
01298 "%ACTIVEUI%/include/SPT--%PAGENAME%.html",
01299 "%ACTIVEUI%/include/%PAGENAME%",
01300 "local/interface/default/include/StdPage%PAGENAME%.tpl",
01301 "local/interface/default/include/StdPage%PAGENAME%.html",
01302 "local/interface/default/include/%PAGENAME%.tpl",
01303 "local/interface/default/include/%PAGENAME%.html",
01304 "local/interface/default/include/SPT--StandardPage%PAGENAME%.tpl",
01305 "local/interface/default/include/SPT--StandardPage%PAGENAME%.html",
01306 "local/interface/default/include/SPT--%PAGENAME%.tpl",
01307 "local/interface/default/include/SPT--%PAGENAME%.html",
01308 "local/interface/default/include/%PAGENAME%",
01309 "interface/default/include/StdPage%PAGENAME%.tpl",
01310 "interface/default/include/StdPage%PAGENAME%.html",
01311 "interface/default/include/%PAGENAME%.tpl",
01312 "interface/default/include/%PAGENAME%.html",
01313 "interface/default/include/SPT--StandardPage%PAGENAME%.tpl",
01314 "interface/default/include/SPT--StandardPage%PAGENAME%.html",
01315 "interface/default/include/SPT--%PAGENAME%.tpl",
01316 "interface/default/include/SPT--%PAGENAME%.html",
01317 "interface/default/include/%PAGENAME%",
01318 );
01319 private $ContentTemplateList = array(
01320 "local/interface/%ACTIVEUI%/%PAGENAME%.tpl",
01321 "local/interface/%ACTIVEUI%/%PAGENAME%.html",
01322 "local/interface/%ACTIVEUI%/SPT--%PAGENAME%.tpl",
01323 "local/interface/%ACTIVEUI%/SPT--%PAGENAME%.html",
01324 "interface/%ACTIVEUI%/%PAGENAME%.tpl",
01325 "interface/%ACTIVEUI%/%PAGENAME%.html",
01326 "interface/%ACTIVEUI%/SPT--%PAGENAME%.tpl",
01327 "interface/%ACTIVEUI%/SPT--%PAGENAME%.html",
01328 "SPTUI--%ACTIVEUI%/%PAGENAME%.tpl",
01329 "SPTUI--%ACTIVEUI%/%PAGENAME%.html",
01330 "SPTUI--%ACTIVEUI%/SPT--%PAGENAME%.tpl",
01331 "SPTUI--%ACTIVEUI%/SPT--%PAGENAME%.html",
01332 "%ACTIVEUI%/%PAGENAME%.tpl",
01333 "%ACTIVEUI%/%PAGENAME%.html",
01334 "%ACTIVEUI%/SPT--%PAGENAME%.tpl",
01335 "%ACTIVEUI%/SPT--%PAGENAME%.html",
01336 "local/interface/default/%PAGENAME%.tpl",
01337 "local/interface/default/%PAGENAME%.html",
01338 "local/interface/default/SPT--%PAGENAME%.tpl",
01339 "local/interface/default/SPT--%PAGENAME%.html",
01340 "interface/default/%PAGENAME%.tpl",
01341 "interface/default/%PAGENAME%.html",
01342 "interface/default/SPT--%PAGENAME%.tpl",
01343 "interface/default/SPT--%PAGENAME%.html",
01344 );
01345 private $ImageFileList = array(
01346 "local/interface/%ACTIVEUI%/images/%PAGENAME%",
01347 "interface/%ACTIVEUI%/images/%PAGENAME%",
01348 "SPTUI--%ACTIVEUI%/images/%PAGENAME%",
01349 "%ACTIVEUI%/images/%PAGENAME%",
01350 "local/interface/default/images/%PAGENAME%",
01351 "interface/default/images/%PAGENAME%",
01352 );
01353 private $FunctionFileList = array(
01354 "local/interface/%ACTIVEUI%/include/%PAGENAME%.php",
01355 "local/interface/%ACTIVEUI%/include/%PAGENAME%.html",
01356 "interface/%ACTIVEUI%/include/%PAGENAME%.php",
01357 "interface/%ACTIVEUI%/include/%PAGENAME%.html",
01358 "SPTUI--%ACTIVEUI%/include/%PAGENAME%.php",
01359 "SPTUI--%ACTIVEUI%/include/%PAGENAME%.html",
01360 "%ACTIVEUI%/include/%PAGENAME%.php",
01361 "%ACTIVEUI%/include/%PAGENAME%.html",
01362 "local/interface/default/include/%PAGENAME%.php",
01363 "local/interface/default/include/%PAGENAME%.html",
01364 "local/include/%PAGENAME%.php",
01365 "local/include/%PAGENAME%.html",
01366 "interface/default/include/%PAGENAME%.php",
01367 "interface/default/include/%PAGENAME%.html",
01368 "include/%PAGENAME%.php",
01369 "include/%PAGENAME%.html",
01370 );
01371
01372 const NOVALUE = ".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
01373 };
01374
01375
01376 ?>