CWIS Developer Documentation
ApplicationFramework.php
Go to the documentation of this file.
1 <?PHP
2 
3 #
4 # FILE: ApplicationFramework.php
5 #
6 # Part of the ScoutLib application support library
7 # Copyright 2009-2012 Edward Almasy and Internet Scout
8 # http://scout.wisc.edu
9 #
10 
16 
17  # ---- PUBLIC INTERFACE --------------------------------------------------
18  /*@(*/
20 
28  function __construct($ObjectDirectories = NULL)
29  {
30  # save execution start time
31  $this->ExecutionStartTime = microtime(TRUE);
32 
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"])
40  : "";
41  $SessionDomain = isset($_SERVER["SERVER_NAME"]) ? $_SERVER["SERVER_NAME"]
42  : isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"]
43  : php_uname("n");
44  session_set_cookie_params(
45  self::$SessionLifetime, $SessionPath, $SessionDomain);
46  session_start();
47 
48  # save object directory search list
49  if ($ObjectDirectories) { $this->AddObjectDirectories($ObjectDirectories); }
50 
51  # set up object file autoloader
52  $this->SetUpObjectAutoloading();
53 
54  # set up function to output any buffered text in case of crash
55  register_shutdown_function(array($this, "OnCrash"));
56 
57  # set up our internal environment
58  $this->DB = new Database();
59 
60  # set up our exception handler
61  set_exception_handler(array($this, "GlobalExceptionHandler"));
62 
63  # load our settings from database
64  $this->LoadSettings();
65 
66  # set PHP maximum execution time
67  $this->MaxExecutionTime($this->Settings["MaxExecTime"]);
68 
69  # register events we handle internally
70  $this->RegisterEvent($this->PeriodicEvents);
71  $this->RegisterEvent($this->UIEvents);
72  }
79  function __destruct()
80  {
81  # if template location cache is flagged to be saved
82  if ($this->SaveTemplateLocationCache)
83  {
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 = "
90  ." NOW() + INTERVAL "
91  .$this->Settings["TemplateLocationCacheInterval"]
92  ." MINUTE");
93  }
94  }
101  function GlobalExceptionHandler($Exception)
102  {
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 />
114  <b>Trace:</b>
115  <blockquote><pre><?PHP print $Exception->getTraceAsString();
116  ?></pre></blockquote>
117  </div>
118  </td></tr></table><?PHP
119 
120  # log exception if possible
121  $LogMsg = "Uncaught exception (".$Exception->getMessage().").";
122  $this->LogError(self::LOGLVL_ERROR, $LogMsg);
123  }
131  function AddObjectDirectories($Dirs)
132  {
133  # for each supplied directory
134  foreach ($Dirs as $Location => $Prefix)
135  {
136  # make sure directory has trailing slash
137  $Location = $Location
138  .((substr($Location, -1) != "/") ? "/" : "");
139 
140  # add directory to directory list
141  self::$ObjectDirectories = array_merge(
142  array($Location => $Prefix),
143  self::$ObjectDirectories);
144  }
145  }
146 
166  function AddImageDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
167  {
168  # add directories to existing image directory list
169  $this->ImageDirList = $this->AddToDirList(
170  $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
171  }
172 
193  function AddIncludeDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
194  {
195  # add directories to existing image directory list
196  $this->IncludeDirList = $this->AddToDirList(
197  $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
198  }
199 
219  function AddInterfaceDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
220  {
221  # add directories to existing image directory list
222  $this->InterfaceDirList = $this->AddToDirList(
223  $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
224  }
225 
245  function AddFunctionDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
246  {
247  # add directories to existing image directory list
248  $this->FunctionDirList = $this->AddToDirList(
249  $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
250  }
251 
257  function SetBrowserDetectionFunc($DetectionFunc)
258  {
259  $this->BrowserDetectFunc = $DetectionFunc;
260  }
261 
268  function AddUnbufferedCallback($Callback, $Parameters=array())
269  {
270  if (is_callable($Callback))
271  {
272  $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
273  }
274  }
275 
282  function TemplateLocationCacheExpirationInterval($NewInterval = -1)
283  {
284  if ($NewInterval >= 0)
285  {
286  $this->Settings["TemplateLocationCacheInterval"] = $NewInterval;
287  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
288  ." SET TemplateLocationCacheInterval = '"
289  .intval($NewInterval)."'");
290  }
291  return $this->Settings["TemplateLocationCacheInterval"];
292  }
293 
299  static function SessionLifetime($NewValue = NULL)
300  {
301  if ($NewValue !== NULL)
302  {
303  self::$SessionLifetime = $NewValue;
304  }
305  return self::$SessionLifetime;
306  }
307 
312  function LoadPage($PageName)
313  {
314  # sanitize incoming page name and save local copy
315  $PageName = preg_replace("/[^a-zA-Z0-9_.-]/", "", $PageName);
316  $this->PageName = $PageName;
317 
318  # buffer any output from includes or PHP file
319  ob_start();
320 
321  # include any files needed to set up execution environment
322  foreach ($this->EnvIncludes as $IncludeFile)
323  {
324  include($IncludeFile);
325  }
326 
327  # signal page load
328  $this->SignalEvent("EVENT_PAGE_LOAD", array("PageName" => $PageName));
329 
330  # signal PHP file load
331  $SignalResult = $this->SignalEvent("EVENT_PHP_FILE_LOAD", array(
332  "PageName" => $PageName));
333 
334  # if signal handler returned new page name value
335  $NewPageName = $PageName;
336  if (($SignalResult["PageName"] != $PageName)
337  && strlen($SignalResult["PageName"]))
338  {
339  # if new page name value is page file
340  if (file_exists($SignalResult["PageName"]))
341  {
342  # use new value for PHP file name
343  $PageFile = $SignalResult["PageName"];
344  }
345  else
346  {
347  # use new value for page name
348  $NewPageName = $SignalResult["PageName"];
349  }
350  }
351 
352  # if we do not already have a PHP file
353  if (!isset($PageFile))
354  {
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");
361  }
362 
363  # load PHP file
364  include($PageFile);
365 
366  # save buffered output to be displayed later after HTML file loads
367  $PageOutput = ob_get_contents();
368  ob_end_clean();
369 
370  # signal PHP file load is complete
371  ob_start();
372  $Context["Variables"] = get_defined_vars();
373  $this->SignalEvent("EVENT_PHP_FILE_LOAD_COMPLETE",
374  array("PageName" => $PageName, "Context" => $Context));
375  $PageCompleteOutput = ob_get_contents();
376  ob_end_clean();
377 
378  # set up for possible TSR (Terminate and Stay Resident :))
379  $ShouldTSR = $this->PrepForTSR();
380 
381  # if PHP file indicated we should autorefresh to somewhere else
382  if ($this->JumpToPage)
383  {
384  if (!strlen(trim($PageOutput)))
385  {
386  ?><html>
387  <head>
388  <meta http-equiv="refresh" content="0; URL=<?PHP
389  print($this->JumpToPage); ?>">
390  </head>
391  <body bgcolor="white">
392  </body>
393  </html><?PHP
394  }
395  }
396  # else if HTML loading is not suppressed
397  elseif (!$this->SuppressHTML)
398  {
399  # set content-type to get rid of diacritic errors
400  header("Content-Type: text/html; charset="
401  .$this->HtmlCharset, TRUE);
402 
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); }
407 
408  # load UI functions
409  $this->LoadUIFunctions();
410 
411  # begin buffering content
412  ob_start();
413 
414  # signal HTML file load
415  $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD", array(
416  "PageName" => $PageName));
417 
418  # if signal handler returned new page name value
419  $NewPageName = $PageName;
420  $PageContentFile = NULL;
421  if (($SignalResult["PageName"] != $PageName)
422  && strlen($SignalResult["PageName"]))
423  {
424  # if new page name value is HTML file
425  if (file_exists($SignalResult["PageName"]))
426  {
427  # use new value for HTML file name
428  $PageContentFile = $SignalResult["PageName"];
429  }
430  else
431  {
432  # use new value for page name
433  $NewPageName = $SignalResult["PageName"];
434  }
435  }
436 
437  # load page content HTML file if available
438  if ($PageContentFile === NULL)
439  {
440  $PageContentFile = $this->FindFile(
441  $this->InterfaceDirList, $NewPageName,
442  array("tpl", "html"));
443  }
444  if ($PageContentFile)
445  {
446  include($PageContentFile);
447  }
448  else
449  {
450  print "<h2>ERROR: No HTML/TPL template found"
451  ." for this page.</h2>";
452  }
453 
454  # signal HTML file load complete
455  $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD_COMPLETE");
456 
457  # stop buffering and save output
458  $PageContentOutput = ob_get_contents();
459  ob_end_clean();
460 
461  # load page start HTML file if available
462  ob_start();
463  $PageStartFile = $this->FindFile($this->IncludeDirList, "Start",
464  array("tpl", "html"), array("StdPage", "StandardPage"));
465  if ($PageStartFile) { include($PageStartFile); }
466  $PageStartOutput = ob_get_contents();
467  ob_end_clean();
468 
469  # load page end HTML file if available
470  ob_start();
471  $PageEndFile = $this->FindFile($this->IncludeDirList, "End",
472  array("tpl", "html"), array("StdPage", "StandardPage"));
473  if ($PageEndFile) { include($PageEndFile); }
474  $PageEndOutput = ob_get_contents();
475  ob_end_clean();
476 
477  # get list of any required files not loaded
478  $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
479 
480  # if a browser detection function has been made available
481  if (is_callable($this->BrowserDetectFunc))
482  {
483  # call function to get browser list
484  $Browsers = call_user_func($this->BrowserDetectFunc);
485 
486  # for each required file
487  $NewRequiredFiles = array();
488  foreach ($RequiredFiles as $File)
489  {
490  # if file name includes browser keyword
491  if (preg_match("/%BROWSER%/", $File))
492  {
493  # for each browser
494  foreach ($Browsers as $Browser)
495  {
496  # substitute in browser name and add to new file list
497  $NewRequiredFiles[] = preg_replace(
498  "/%BROWSER%/", $Browser, $File);
499  }
500  }
501  else
502  {
503  # add to new file list
504  $NewRequiredFiles[] = $File;
505  }
506  }
507  $RequiredFiles = $NewRequiredFiles;
508  }
509 
510  # for each required file
511  foreach ($RequiredFiles as $File)
512  {
513  # locate specific file to use
514  $FilePath = $this->GUIFile($File);
515 
516  # if file was found
517  if ($FilePath)
518  {
519  # determine file type
520  $NamePieces = explode(".", $File);
521  $FileSuffix = strtolower(array_pop($NamePieces));
522 
523  # add file to HTML output based on file type
524  $FilePath = htmlspecialchars($FilePath);
525  switch ($FileSuffix)
526  {
527  case "js":
528  $Tag = '<script type="text/javascript" src="'
529  .$FilePath.'"></script>';
530  $PageEndOutput = preg_replace(
531  "#</body>#i", $Tag."\n</body>", $PageEndOutput, 1);
532  break;
533 
534  case "css":
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);
539  break;
540  }
541  }
542  }
543 
544  # write out page
545  print $PageStartOutput.$PageContentOutput.$PageEndOutput;
546  }
547 
548  # run any post-processing routines
549  foreach ($this->PostProcessingFuncs as $Func)
550  {
551  call_user_func_array($Func["FunctionName"], $Func["Arguments"]);
552  }
553 
554  # write out any output buffered from page code execution
555  if (strlen($PageOutput))
556  {
557  if (!$this->SuppressHTML)
558  {
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
563  }
564  if ($this->JumpToPage)
565  {
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 />
569  <b>Jump Target:</b>
570  <i><?PHP print($this->JumpToPage); ?></i></div><?PHP
571  }
572  print $PageOutput;
573  if (!$this->SuppressHTML)
574  {
575  ?></td></tr></table><?PHP
576  }
577  }
578 
579  # write out any output buffered from the page code execution complete signal
580  if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
581  {
582  print $PageCompleteOutput;
583  }
584 
585  # execute callbacks that should not have their output buffered
586  foreach ($this->UnbufferedCallbacks as $Callback)
587  {
588  call_user_func_array($Callback[0], $Callback[1]);
589  }
590 
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(); }
594  }
595 
601  function GetPageName()
602  {
603  return $this->PageName;
604  }
605 
612  function SetJumpToPage($Page)
613  {
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))
621  {
622  $this->JumpToPage = "index.php?P=".$Page;
623  }
624  else
625  {
626  $this->JumpToPage = $Page;
627  }
628  }
629 
634  function JumpToPageIsSet()
635  {
636  return ($this->JumpToPage === NULL) ? FALSE : TRUE;
637  }
638 
648  function HtmlCharset($NewSetting = NULL)
649  {
650  if ($NewSetting !== NULL) { $this->HtmlCharset = $NewSetting; }
651  return $this->HtmlCharset;
652  }
653 
660  function SuppressHTMLOutput($NewSetting = TRUE)
661  {
662  $this->SuppressHTML = $NewSetting;
663  }
664 
671  function ActiveUserInterface($UIName = NULL)
672  {
673  if ($UIName !== NULL)
674  {
675  $this->ActiveUI = preg_replace("/^SPTUI--/", "", $UIName);
676  }
677  return $this->ActiveUI;
678  }
679 
695  function AddPostProcessingCall($FunctionName,
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)
699  {
700  $FuncIndex = count($this->PostProcessingFuncs);
701  $this->PostProcessingFuncs[$FuncIndex]["FunctionName"] = $FunctionName;
702  $this->PostProcessingFuncs[$FuncIndex]["Arguments"] = array();
703  $Index = 1;
704  while (isset(${"Arg".$Index}) && (${"Arg".$Index} !== self::NOVALUE))
705  {
706  $this->PostProcessingFuncs[$FuncIndex]["Arguments"][$Index]
707  =& ${"Arg".$Index};
708  $Index++;
709  }
710  }
711 
717  function AddEnvInclude($FileName)
718  {
719  $this->EnvIncludes[] = $FileName;
720  }
721 
728  function GUIFile($FileName)
729  {
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;
733 
734  # search for file
735  $FoundFileName = $this->FindFile($DirList, $FileName);
736 
737  # add non-image files to list of found files (used for required files loading)
738  if (!$FileIsImage) { $this->FoundUIFiles[] = basename($FoundFileName); }
739 
740  # return file name to caller
741  return $FoundFileName;
742  }
743 
753  function PUIFile($FileName)
754  {
755  $FullFileName = $this->GUIFile($FileName);
756  if ($FullFileName) { print($FullFileName); }
757  }
758 
766  function RequireUIFile($FileName)
767  {
768  $this->AdditionalRequiredUIFiles[] = $FileName;
769  }
770 
779  function LoadFunction($Callback)
780  {
781  # if specified function is not currently available
782  if (!is_callable($Callback))
783  {
784  # if function info looks legal
785  if (is_string($Callback) && strlen($Callback))
786  {
787  # start with function directory list
788  $Locations = $this->FunctionDirList;
789 
790  # add object directories to list
791  $Locations = array_merge($Locations, self::$ObjectDirectories);
792 
793  # look for function file
794  $FunctionFileName = $this->FindFile($Locations, "F-".$Callback,
795  array("php", "html"));
796 
797  # if function file was found
798  if ($FunctionFileName)
799  {
800  # load function file
801  include_once($FunctionFileName);
802  }
803  else
804  {
805  # log error indicating function load failed
806  $this->LogError(self::LOGLVL_ERROR, "Unable to load function"
807  ." for callback \"".$Callback."\".");
808  }
809  }
810  else
811  {
812  # log error indicating specified function info was bad
813  $this->LogError(self::LOGLVL_ERROR, "Unloadable callback value"
814  ." (".$Callback.")"
815  ." passed to AF::LoadFunction().");
816  }
817  }
818 
819  # report to caller whether function load succeeded
820  return is_callable($Callback);
821  }
822 
828  {
829  return microtime(TRUE) - $this->ExecutionStartTime;
830  }
831 
837  {
838  return ini_get("max_execution_time") - $this->GetElapsedExecutionTime();
839  }
840 
845  function HtaccessSupport()
846  {
847  # HTACCESS_SUPPORT is set in the .htaccess file
848  return isset($_SERVER["HTACCESS_SUPPORT"]);
849  }
850 
862  function LogError($Level, $Msg)
863  {
864  # if error level is at or below current logging level
865  if ($this->Settings["LoggingLevel"] >= $Level)
866  {
867  # attempt to log error message
868  $Result = $this->LogMessage($Level, $Msg);
869 
870  # if logging attempt failed and level indicated significant error
871  if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
872  {
873  # throw exception about inability to log error
874  static $AlreadyThrewException = FALSE;
875  if (!$AlreadyThrewException)
876  {
877  $AlreadyThrewException = TRUE;
878  throw new Exception("Unable to log error (".$Level.": ".$Msg.").");
879  }
880  }
881 
882  # report to caller whether message was logged
883  return $Result;
884  }
885  else
886  {
887  # report to caller that message was not logged
888  return FALSE;
889  }
890  }
891 
903  function LogMessage($Level, $Msg)
904  {
905  # if message level is at or below current logging level
906  if ($this->Settings["LoggingLevel"] >= $Level)
907  {
908  # attempt to open log file
909  $FHndl = @fopen("local/logs/cwis.log", "a");
910 
911  # if log file could not be open
912  if ($FHndl === FALSE)
913  {
914  # report to caller that message was not logged
915  return FALSE;
916  }
917  else
918  {
919  # format log entry
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",
927  );
928  $LogEntry = date("Y-m-d H:i:s")
929  ." ".($this->RunningInBackground ? "B" : "F")
930  ." ".$ErrorAbbrevs[$Level]
931  ." ".$Msg;
932 
933  # write entry to log
934  $Success = fputs($FHndl, $LogEntry."\n");
935 
936  # close log file
937  fclose($FHndl);
938 
939  # report to caller whether message was logged
940  return ($Success === FALSE) ? FALSE : TRUE;
941  }
942  }
943  else
944  {
945  # report to caller that message was not logged
946  return FALSE;
947  }
948  }
949 
971  function LoggingLevel($NewValue = NULL)
972  {
973  # if new logging level was specified
974  if ($NewValue !== NULL)
975  {
976  # constrain new level to within legal bounds and store locally
977  $this->Settings["LoggingLevel"] = max(min($NewValue, 6), 1);
978 
979  # save new logging level in database
980  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
981  ." SET LoggingLevel = "
982  .intval($this->Settings["LoggingLevel"]));
983  }
984 
985  # report current logging level to caller
986  return $this->Settings["LoggingLevel"];
987  }
988 
993  const LOGLVL_TRACE = 6;
998  const LOGLVL_DEBUG = 5;
1004  const LOGLVL_INFO = 4;
1009  const LOGLVL_WARNING = 3;
1015  const LOGLVL_ERROR = 2;
1020  const LOGLVL_FATAL = 1;
1021 
1022  /*@)*/ /* Application Framework */
1023 
1024  # ---- Event Handling ----------------------------------------------------
1025  /*@(*/
1027 
1037  const EVENTTYPE_CHAIN = 2;
1043  const EVENTTYPE_FIRST = 3;
1051  const EVENTTYPE_NAMED = 4;
1052 
1054  const ORDER_FIRST = 1;
1056  const ORDER_MIDDLE = 2;
1058  const ORDER_LAST = 3;
1059 
1068  function RegisterEvent($EventsOrEventName, $EventType = NULL)
1069  {
1070  # convert parameters to array if not already in that form
1071  $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1072  : array($EventsOrEventName => $Type);
1073 
1074  # for each event
1075  foreach ($Events as $Name => $Type)
1076  {
1077  # store event information
1078  $this->RegisteredEvents[$Name]["Type"] = $Type;
1079  $this->RegisteredEvents[$Name]["Hooks"] = array();
1080  }
1081  }
1082 
1088  function IsRegisteredEvent($EventName)
1089  {
1090  return array_key_exists($EventName, $this->RegisteredEvents)
1091  ? TRUE : FALSE;
1092  }
1093 
1107  function HookEvent($EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
1108  {
1109  # convert parameters to array if not already in that form
1110  $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1111  : array($EventsOrEventName => $Callback);
1112 
1113  # for each event
1114  $Success = TRUE;
1115  foreach ($Events as $EventName => $EventCallback)
1116  {
1117  # if callback is valid
1118  if (is_callable($EventCallback))
1119  {
1120  # if this is a periodic event we process internally
1121  if (isset($this->PeriodicEvents[$EventName]))
1122  {
1123  # process event now
1124  $this->ProcessPeriodicEvent($EventName, $EventCallback);
1125  }
1126  # if specified event has been registered
1127  elseif (isset($this->RegisteredEvents[$EventName]))
1128  {
1129  # add callback for event
1130  $this->RegisteredEvents[$EventName]["Hooks"][]
1131  = array("Callback" => $EventCallback, "Order" => $Order);
1132 
1133  # sort callbacks by order
1134  if (count($this->RegisteredEvents[$EventName]["Hooks"]) > 1)
1135  {
1136  usort($this->RegisteredEvents[$EventName]["Hooks"],
1137  array("ApplicationFramework", "HookEvent_OrderCompare"));
1138  }
1139  }
1140  else
1141  {
1142  $Success = FALSE;
1143  }
1144  }
1145  else
1146  {
1147  $Success = FALSE;
1148  }
1149  }
1150 
1151  # report to caller whether all callbacks were hooked
1152  return $Success;
1153  }
1154  private static function HookEvent_OrderCompare($A, $B)
1155  {
1156  if ($A["Order"] == $B["Order"]) { return 0; }
1157  return ($A["Order"] < $B["Order"]) ? -1 : 1;
1158  }
1159 
1168  function SignalEvent($EventName, $Parameters = NULL)
1169  {
1170  $ReturnValue = NULL;
1171 
1172  # if event has been registered
1173  if (isset($this->RegisteredEvents[$EventName]))
1174  {
1175  # set up default return value (if not NULL)
1176  switch ($this->RegisteredEvents[$EventName]["Type"])
1177  {
1178  case self::EVENTTYPE_CHAIN:
1179  $ReturnValue = $Parameters;
1180  break;
1181 
1182  case self::EVENTTYPE_NAMED:
1183  $ReturnValue = array();
1184  break;
1185  }
1186 
1187  # for each callback for this event
1188  foreach ($this->RegisteredEvents[$EventName]["Hooks"] as $Hook)
1189  {
1190  # invoke callback
1191  $Callback = $Hook["Callback"];
1192  $Result = ($Parameters !== NULL)
1193  ? call_user_func_array($Callback, $Parameters)
1194  : call_user_func($Callback);
1195 
1196  # process return value based on event type
1197  switch ($this->RegisteredEvents[$EventName]["Type"])
1198  {
1199  case self::EVENTTYPE_CHAIN:
1200  if ($Result !== NULL)
1201  {
1202  foreach ($Parameters as $Index => $Value)
1203  {
1204  if (array_key_exists($Index, $Result))
1205  {
1206  $Parameters[$Index] = $Result[$Index];
1207  }
1208  }
1209  $ReturnValue = $Parameters;
1210  }
1211  break;
1212 
1213  case self::EVENTTYPE_FIRST:
1214  if ($Result !== NULL)
1215  {
1216  $ReturnValue = $Result;
1217  break 2;
1218  }
1219  break;
1220 
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]
1226  : $Callback;
1227  $ReturnValue[$CallbackName] = $Result;
1228  break;
1229 
1230  default:
1231  break;
1232  }
1233  }
1234  }
1235  else
1236  {
1237  $this->LogError(self::LOGLVL_WARNING,
1238  "Unregistered event signaled (".$EventName.").");
1239  }
1240 
1241  # return value if any to caller
1242  return $ReturnValue;
1243  }
1244 
1250  function IsStaticOnlyEvent($EventName)
1251  {
1252  return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
1253  }
1254 
1255  /*@)*/ /* Event Handling */
1256 
1257  # ---- Task Management ---------------------------------------------------
1258  /*@(*/
1260 
1262  const PRIORITY_HIGH = 1;
1264  const PRIORITY_MEDIUM = 2;
1266  const PRIORITY_LOW = 3;
1269 
1282  function QueueTask($Callback, $Parameters = NULL,
1283  $Priority = self::PRIORITY_MEDIUM, $Description = "")
1284  {
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)."')");
1292  }
1293 
1311  function QueueUniqueTask($Callback, $Parameters = NULL,
1312  $Priority = self::PRIORITY_MEDIUM, $Description = "")
1313  {
1314  if ($this->TaskIsInQueue($Callback, $Parameters))
1315  {
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)
1321  {
1322  $Record = $this->DB->FetchRow();
1323  if ($Record["Priority"] > $Priority)
1324  {
1325  $this->DB->Query("UPDATE TaskQueue"
1326  ." SET Priority = ".intval($Priority)
1327  ." WHERE TaskId = ".intval($Record["TaskId"]));
1328  }
1329  }
1330  return FALSE;
1331  }
1332  else
1333  {
1334  $this->QueueTask($Callback, $Parameters, $Priority, $Description);
1335  return TRUE;
1336  }
1337  }
1338 
1348  function TaskIsInQueue($Callback, $Parameters = NULL)
1349  {
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))."'" : ""),
1354  "FoundCount")
1355  + $this->DB->Query("SELECT COUNT(*) AS FoundCount FROM RunningTasks"
1356  ." WHERE Callback = '".addslashes(serialize($Callback))."'"
1357  .($Parameters ? " AND Parameters = '"
1358  .addslashes(serialize($Parameters))."'" : ""),
1359  "FoundCount");
1360  return ($FoundCount ? TRUE : FALSE);
1361  }
1362 
1368  function GetTaskQueueSize($Priority = NULL)
1369  {
1370  return $this->DB->Query("SELECT COUNT(*) AS QueueSize FROM TaskQueue"
1371  .($Priority ? " WHERE Priority = ".intval($Priority) : ""),
1372  "QueueSize");
1373  }
1374 
1382  function GetQueuedTaskList($Count = 100, $Offset = 0)
1383  {
1384  return $this->GetTaskList("SELECT * FROM TaskQueue"
1385  ." ORDER BY Priority, TaskId ", $Count, $Offset);
1386  }
1387 
1395  function GetRunningTaskList($Count = 100, $Offset = 0)
1396  {
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);
1401  }
1402 
1410  function GetOrphanedTaskList($Count = 100, $Offset = 0)
1411  {
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);
1416  }
1417 
1423  {
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")))."'",
1427  "Count");
1428  }
1429 
1435  function ReQueueOrphanedTask($TaskId, $NewPriority = NULL)
1436  {
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)
1443  {
1444  $NewTaskId = $this->DB->LastInsertId("TaskQueue");
1445  $this->DB->Query("UPDATE TaskQueue SET Priority = "
1446  .intval($NewPriority)
1447  ." WHERE TaskId = ".intval($NewTaskId));
1448  }
1449  $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
1450  $this->DB->Query("UNLOCK TABLES");
1451  }
1452 
1457  function DeleteTask($TaskId)
1458  {
1459  $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
1460  $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
1461  }
1462 
1470  function GetTask($TaskId)
1471  {
1472  # assume task will not be found
1473  $Task = NULL;
1474 
1475  # look for task in task queue
1476  $this->DB->Query("SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
1477 
1478  # if task was not found in queue
1479  if (!$this->DB->NumRowsSelected())
1480  {
1481  # look for task in running task list
1482  $this->DB->Query("SELECT * FROM RunningTasks WHERE TaskId = "
1483  .intval($TaskId));
1484  }
1485 
1486  # if task was found
1487  if ($this->DB->NumRowsSelected())
1488  {
1489  # if task was periodic
1490  $Row = $this->DB->FetchRow();
1491  if ($Row["Callback"] ==
1492  serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
1493  {
1494  # unpack periodic task callback
1495  $WrappedCallback = unserialize($Row["Parameters"]);
1496  $Task["Callback"] = $WrappedCallback[1];
1497  $Task["Parameters"] = $WrappedCallback[2];
1498  }
1499  else
1500  {
1501  # unpack task callback and parameters
1502  $Task["Callback"] = unserialize($Row["Callback"]);
1503  $Task["Parameters"] = unserialize($Row["Parameters"]);
1504  }
1505  }
1506 
1507  # return task to caller
1508  return $Task;
1509  }
1510 
1518  function TaskExecutionEnabled($NewValue = NULL)
1519  {
1520  if ($NewValue !== NULL)
1521  {
1522  $this->Settings["TaskExecutionEnabled"] = $NewValue ? 1 : 0;
1523  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
1524  ." SET TaskExecutionEnabled = "
1525  .$this->Settings["TaskExecutionEnabled"]);
1526  }
1527  return $this->Settings["TaskExecutionEnabled"];
1528  }
1529 
1535  function MaxTasks($NewValue = NULL)
1536  {
1537  if (func_num_args() && ($NewValue >= 1))
1538  {
1539  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
1540  ." SET MaxTasksRunning = ".intval($NewValue));
1541  $this->Settings["MaxTasksRunning"] = intval($NewValue);
1542  }
1543  return $this->Settings["MaxTasksRunning"];
1544  }
1545 
1553  function MaxExecutionTime($NewValue = NULL)
1554  {
1555  if (func_num_args() && !ini_get("safe_mode"))
1556  {
1557  if ($NewValue != $this->Settings["MaxExecTime"])
1558  {
1559  $this->Settings["MaxExecTime"] = max($NewValue, 5);
1560  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
1561  ." SET MaxExecTime = '"
1562  .intval($this->Settings["MaxExecTime"])."'");
1563  }
1564  ini_set("max_execution_time", $this->Settings["MaxExecTime"]);
1565  set_time_limit($this->Settings["MaxExecTime"]);
1566  }
1567  return ini_get("max_execution_time");
1568  }
1569 
1570  /*@)*/ /* Task Management */
1571 
1572  # ---- Backward Compatibility --------------------------------------------
1573 
1578  function FindCommonTemplate($BaseName)
1579  {
1580  return $this->FindFile(
1581  $this->IncludeDirList, $BaseName, array("tpl", "html"));
1582  }
1583  /*@(*/
1585 
1586 
1587  # ---- PRIVATE INTERFACE -------------------------------------------------
1588 
1589  private $ActiveUI = "default";
1590  private $BrowserDetectFunc;
1591  private $DB;
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;
1599  private $PageName;
1600  private $MaxRunningTasksToTrack = 250;
1601  private $PostProcessingFuncs = array();
1602  private $RunningInBackground = FALSE;
1603  private $RunningTask;
1604  private $Settings;
1605  private $SuppressHTML = FALSE;
1606  private $SaveTemplateLocationCache = FALSE;
1607  private $UnbufferedCallbacks = array();
1608 
1609  private static $ObjectDirectories = array();
1610  private static $SessionLifetime = 1440;
1611 
1612  # set to TRUE to not close browser connection before running
1613  # background tasks (useful when debugging)
1614  private $NoTSR = FALSE;
1615 
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,
1622  );
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,
1629  );
1630 
1634  private function LoadSettings()
1635  {
1636  # read settings in from database
1637  $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
1638  $this->Settings = $this->DB->FetchRow();
1639 
1640  # if settings were not previously initialized
1641  if (!$this->Settings)
1642  {
1643  # initialize settings in database
1644  $this->DB->Query("INSERT INTO ApplicationFrameworkSettings"
1645  ." (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
1646 
1647  # read new settings in from database
1648  $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
1649  $this->Settings = $this->DB->FetchRow();
1650  }
1651 
1652  # if template location cache has been saved to database
1653  if (isset($this->Settings["TemplateLocationCache"]))
1654  {
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();
1659  }
1660  else
1661  {
1662  # start with empty cache
1663  $this->Settings["TemplateLocationCache"] = array();
1664  }
1665  }
1666 
1683  private function FindFile($DirectoryList, $BaseName,
1684  $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
1685  {
1686  # generate template cache index for this page
1687  $CacheIndex = md5(serialize($DirectoryList))
1688  .":".$this->ActiveUI.":".$BaseName;
1689 
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"])))
1697  {
1698  # use template location from cache
1699  $FoundFileName = $this->Settings[
1700  "TemplateLocationCache"][$CacheIndex];
1701  }
1702  else
1703  {
1704  # if suffixes specified and base name does not include suffix
1705  if (count($PossibleSuffixes)
1706  && !preg_match("/\.[a-zA-Z0-9]+$/", $BaseName))
1707  {
1708  # add versions of file names with suffixes to file name list
1709  $FileNames = array();
1710  foreach ($PossibleSuffixes as $Suffix)
1711  {
1712  $FileNames[] = $BaseName.".".$Suffix;
1713  }
1714  }
1715  else
1716  {
1717  # use base name as file name
1718  $FileNames = array($BaseName);
1719  }
1720 
1721  # if prefixes specified
1722  if (count($PossiblePrefixes))
1723  {
1724  # add versions of file names with prefixes to file name list
1725  $NewFileNames = array();
1726  foreach ($FileNames as $FileName)
1727  {
1728  foreach ($PossiblePrefixes as $Prefix)
1729  {
1730  $NewFileNames[] = $Prefix.$FileName;
1731  }
1732  }
1733  $FileNames = $NewFileNames;
1734  }
1735 
1736  # for each possible location
1737  $FoundFileName = NULL;
1738  foreach ($DirectoryList as $Dir)
1739  {
1740  # substitute active UI name into path
1741  $Dir = str_replace("%ACTIVEUI%", $this->ActiveUI, $Dir);
1742 
1743  # for each possible file name
1744  foreach ($FileNames as $File)
1745  {
1746  # if template is found at location
1747  if (file_exists($Dir.$File))
1748  {
1749  # save full template file name and stop looking
1750  $FoundFileName = $Dir.$File;
1751  break 2;
1752  }
1753  }
1754  }
1755 
1756  # save location in cache
1757  $this->Settings["TemplateLocationCache"][$CacheIndex]
1758  = $FoundFileName;
1759 
1760  # set flag indicating that cache should be saved
1761  $this->SaveTemplateLocationCache = TRUE;
1762  }
1763 
1764  # return full template file name to caller
1765  return $FoundFileName;
1766  }
1767 
1774  private function GetRequiredFilesNotYetLoaded($PageContentFile)
1775  {
1776  # start out assuming no files required
1777  $RequiredFiles = array();
1778 
1779  # if page content file supplied
1780  if ($PageContentFile)
1781  {
1782  # if file containing list of required files is available
1783  $Path = dirname($PageContentFile);
1784  $RequireListFile = $Path."/REQUIRES";
1785  if (file_exists($RequireListFile))
1786  {
1787  # read in list of required files
1788  $RequestedFiles = file($RequireListFile);
1789 
1790  # for each line in required file list
1791  foreach ($RequestedFiles as $Line)
1792  {
1793  # if line is not a comment
1794  $Line = trim($Line);
1795  if (!preg_match("/^#/", $Line))
1796  {
1797  # if file has not already been loaded
1798  if (!in_array($Line, $this->FoundUIFiles))
1799  {
1800  # add to list of required files
1801  $RequiredFiles[] = $Line;
1802  }
1803  }
1804  }
1805  }
1806  }
1807 
1808  # add in additional required files if any
1809  if (count($this->AdditionalRequiredUIFiles))
1810  {
1811  # make sure there are no duplicates
1812  $AdditionalRequiredUIFiles = array_unique(
1813  $this->AdditionalRequiredUIFiles);
1814 
1815  $RequiredFiles = array_merge(
1816  $RequiredFiles, $AdditionalRequiredUIFiles);
1817  }
1818 
1819  # return list of required files to caller
1820  return $RequiredFiles;
1821  }
1822 
1823  private function SetUpObjectAutoloading()
1824  {
1825  function __autoload($ClassName)
1826  {
1827  ApplicationFramework::AutoloadObjects($ClassName);
1828  }
1829  }
1830 
1832  static function AutoloadObjects($ClassName)
1833  {
1834  foreach (self::$ObjectDirectories as $Location => $Prefix)
1835  {
1836  $FileNames = scandir($Location);
1837  $TargetName = strtolower($Prefix.$ClassName.".php");
1838  foreach ($FileNames as $FileName)
1839  {
1840  if (strtolower($FileName) == $TargetName)
1841  {
1842  require_once($Location.$FileName);
1843  break 2;
1844  }
1845  }
1846  }
1847  }
1850  private function LoadUIFunctions()
1851  {
1852  $Dirs = array(
1853  "local/interface/%ACTIVEUI%/include",
1854  "interface/%ACTIVEUI%/include",
1855  "local/interface/default/include",
1856  "interface/default/include",
1857  );
1858  foreach ($Dirs as $Dir)
1859  {
1860  $Dir = str_replace("%ACTIVEUI%", $this->ActiveUI, $Dir);
1861  if (is_dir($Dir))
1862  {
1863  $FileNames = scandir($Dir);
1864  foreach ($FileNames as $FileName)
1865  {
1866  if (preg_match("/^F-([A-Za-z_]+)\.php/", $FileName, $Matches)
1867  || preg_match("/^F-([A-Za-z_]+)\.html/", $FileName, $Matches))
1868  {
1869  if (!function_exists($Matches[1]))
1870  {
1871  include_once($Dir."/".$FileName);
1872  }
1873  }
1874  }
1875  }
1876  }
1877  }
1878 
1879  private function ProcessPeriodicEvent($EventName, $Callback)
1880  {
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");
1885 
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,
1893  );
1894  $ShouldExecute = (($LastRun === NULL)
1895  || (time() > (strtotime($LastRun) + $EventPeriods[$EventName])))
1896  ? TRUE : FALSE;
1897 
1898  # if event should run
1899  if ($ShouldExecute)
1900  {
1901  # add event to task queue
1902  $WrapperCallback = array("ApplicationFramework", "PeriodicEventWrapper");
1903  $WrapperParameters = array(
1904  $EventName, $Callback, array("LastRunAt" => $LastRun));
1905  $this->QueueUniqueTask($WrapperCallback, $WrapperParameters);
1906  }
1907  }
1908 
1909  private static function PeriodicEventWrapper($EventName, $Callback, $Parameters)
1910  {
1911  static $DB;
1912  if (!isset($DB)) { $DB = new Database(); }
1913 
1914  # run event
1915  $ReturnVal = call_user_func_array($Callback, $Parameters);
1916 
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"))
1921  {
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))."'"
1926  : "NOW()")
1927  ." WHERE Signature = '".addslashes($Signature)."'");
1928  }
1929  else
1930  {
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))."'"
1936  : "NOW()").")");
1937  }
1938  }
1939 
1940  private static function GetCallbackSignature($Callback)
1941  {
1942  return !is_array($Callback) ? $Callback
1943  : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
1944  ."::".$Callback[1];
1945  }
1946 
1947  private function PrepForTSR()
1948  {
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))
1956  && $this->GetTaskQueueSize()
1957  && $this->Settings["TaskExecutionEnabled"])
1958  {
1959  # begin buffering output for TSR
1960  ob_start();
1961 
1962  # let caller know it is time to launch another task
1963  return TRUE;
1964  }
1965  else
1966  {
1967  # let caller know it is not time to launch another task
1968  return FALSE;
1969  }
1970  }
1971 
1972  private function LaunchTSR()
1973  {
1974  # set headers to close out connection to browser
1975  if (!$this->NoTSR)
1976  {
1977  ignore_user_abort(TRUE);
1978  header("Connection: close");
1979  header("Content-Length: ".ob_get_length());
1980  }
1981 
1982  # output buffered content
1983  ob_end_flush();
1984  flush();
1985 
1986  # write out any outstanding data and end HTTP session
1987  session_write_close();
1988 
1989  # set flag indicating that we are now running in background
1990  $this->RunningInBackground = TRUE;
1991 
1992  # if there is still a task in the queue
1993  if ($this->GetTaskQueueSize())
1994  {
1995  # turn on output buffering to (hopefully) record any crash output
1996  ob_start();
1997 
1998  # lock tables and grab last task run time to double check
1999  $this->DB->Query("LOCK TABLES ApplicationFrameworkSettings WRITE");
2000  $this->LoadSettings();
2001 
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))
2006  {
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");
2011 
2012  # run tasks while there is a task in the queue and enough time left
2013  do
2014  {
2015  # run the next task
2016  $this->RunNextTask();
2017  }
2018  while ($this->GetTaskQueueSize()
2019  && ($this->GetSecondsBeforeTimeout() > 65));
2020  }
2021  else
2022  {
2023  # release tables
2024  $this->DB->Query("UNLOCK TABLES");
2025  }
2026  }
2027  }
2028 
2036  private function GetTaskList($DBQuery, $Count, $Offset)
2037  {
2038  $this->DB->Query($DBQuery." LIMIT ".intval($Offset).",".intval($Count));
2039  $Tasks = array();
2040  while ($Row = $this->DB->FetchRow())
2041  {
2042  $Tasks[$Row["TaskId"]] = $Row;
2043  if ($Row["Callback"] ==
2044  serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
2045  {
2046  $WrappedCallback = unserialize($Row["Parameters"]);
2047  $Tasks[$Row["TaskId"]]["Callback"] = $WrappedCallback[1];
2048  $Tasks[$Row["TaskId"]]["Parameters"] = NULL;
2049  }
2050  else
2051  {
2052  $Tasks[$Row["TaskId"]]["Callback"] = unserialize($Row["Callback"]);
2053  $Tasks[$Row["TaskId"]]["Parameters"] = unserialize($Row["Parameters"]);
2054  }
2055  }
2056  return $Tasks;
2057  }
2058 
2062  private function RunNextTask()
2063  {
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();
2067 
2068  # if there was a task available
2069  if ($Task)
2070  {
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"]));
2078 
2079  # unpack stored task info
2080  $Callback = unserialize($Task["Callback"]);
2081  $Parameters = unserialize($Task["Parameters"]);
2082 
2083  # attempt to load task callback if not already available
2084  $this->LoadFunction($Callback);
2085 
2086  # run task
2087  $this->RunningTask = $Task;
2088  if ($Parameters)
2089  {
2090  call_user_func_array($Callback, $Parameters);
2091  }
2092  else
2093  {
2094  call_user_func($Callback);
2095  }
2096  unset($this->RunningTask);
2097 
2098  # remove task from running tasks list
2099  $this->DB->Query("DELETE FROM RunningTasks"
2100  ." WHERE TaskId = ".intval($Task["TaskId"]));
2101 
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)
2106  {
2107  $this->DB->Query("DELETE FROM RunningTasks ORDER BY StartedAt"
2108  ." LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
2109  }
2110  }
2111  }
2112 
2118  function OnCrash()
2119  {
2120  if (isset($this->RunningTask))
2121  {
2122  if (function_exists("error_get_last"))
2123  {
2124  $CrashInfo["LastError"] = error_get_last();
2125  }
2126  if (ob_get_length() !== FALSE)
2127  {
2128  $CrashInfo["OutputBuffer"] = ob_get_contents();
2129  }
2130  if (isset($CrashInfo))
2131  {
2132  $DB = new Database();
2133  $DB->Query("UPDATE RunningTasks SET CrashInfo = '"
2134  .addslashes(serialize($CrashInfo))
2135  ."' WHERE TaskId = ".intval($this->RunningTask["TaskId"]));
2136  }
2137  }
2138 
2139  print("\n");
2140  return;
2141 
2142  if (ob_get_length() !== FALSE)
2143  {
2144  ?>
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
2147  ob_end_flush();
2148  ?></td></tr></table><?PHP
2149  }
2150  }
2151 
2168  private function AddToDirList($DirList, $Dir, $SearchLast, $SkipSlashCheck)
2169  {
2170  # convert incoming directory to array of directories (if needed)
2171  $Dirs = is_array($Dir) ? $Dir : array($Dir);
2172 
2173  # reverse array so directories are searched in specified order
2174  $Dirs = array_reverse($Dirs);
2175 
2176  # for each directory
2177  foreach ($Dirs as $Location)
2178  {
2179  # make sure directory includes trailing slash
2180  if (!$SkipSlashCheck)
2181  {
2182  $Location = $Location
2183  .((substr($Location, -1) != "/") ? "/" : "");
2184  }
2185 
2186  # remove directory from list if already present
2187  if (in_array($Location, $DirList))
2188  {
2189  $DirList = array_diff(
2190  $DirList, array($Location));
2191  }
2192 
2193  # add directory to list of directories
2194  if ($SearchLast)
2195  {
2196  array_push($DirList, $Location);
2197  }
2198  else
2199  {
2200  array_unshift($DirList, $Location);
2201  }
2202  }
2203 
2204  # return updated directory list to caller
2205  return $DirList;
2206  }
2207 
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/",
2214  );
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/",
2222  );
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/",
2229  );
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/",
2236  "local/include/",
2237  "include/",
2238  );
2239 
2240  const NOVALUE = ".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
2241 };
2242 
2243 
2244 ?>