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 
25  function __construct()
26  {
27  # save execution start time
28  $this->ExecutionStartTime = microtime(TRUE);
29 
30  # begin/restore PHP session
31  $SessionPath = isset($_SERVER["REQUEST_URI"])
32  ? dirname($_SERVER["REQUEST_URI"])
33  : isset($_SERVER["SCRIPT_NAME"])
34  ? dirname($_SERVER["SCRIPT_NAME"])
35  : isset($_SERVER["PHP_SELF"])
36  ? dirname($_SERVER["PHP_SELF"])
37  : "";
38  $SessionDomain = isset($_SERVER["SERVER_NAME"]) ? $_SERVER["SERVER_NAME"]
39  : isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"]
40  : php_uname("n");
41  $SessionStorage = session_save_path()
42  ."/".self::$AppName."_".md5($SessionDomain.$SessionPath);
43  if (!is_dir($SessionStorage)) { mkdir($SessionStorage, 0700 ); }
44  session_save_path($SessionStorage);
45  ini_set("session.gc_maxlifetime", self::$SessionLifetime);
46  session_set_cookie_params(
47  self::$SessionLifetime, $SessionPath, $SessionDomain);
48  session_start();
49 
50  # set up object file autoloader
51  $this->SetUpObjectAutoloading();
52 
53  # set up function to output any buffered text in case of crash
54  register_shutdown_function(array($this, "OnCrash"));
55 
56  # set up our internal environment
57  $this->DB = new Database();
58 
59  # set up our exception handler
60  set_exception_handler(array($this, "GlobalExceptionHandler"));
61 
62  # load our settings from database
63  $this->LoadSettings();
64 
65  # set PHP maximum execution time
66  $this->MaxExecutionTime($this->Settings["MaxExecTime"]);
67 
68  # register events we handle internally
69  $this->RegisterEvent($this->PeriodicEvents);
70  $this->RegisterEvent($this->UIEvents);
71  }
78  function __destruct()
79  {
80  # if template location cache is flagged to be saved
81  if ($this->SaveTemplateLocationCache)
82  {
83  # write template location cache out and update cache expiration
84  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
85  ." SET TemplateLocationCache = '"
86  .addslashes(serialize(
87  $this->Settings["TemplateLocationCache"]))."',"
88  ." TemplateLocationCacheExpiration = "
89  ." NOW() + INTERVAL "
90  .$this->Settings["TemplateLocationCacheInterval"]
91  ." MINUTE");
92  }
93  }
100  function GlobalExceptionHandler($Exception)
101  {
102  # display exception info
103  $Location = $Exception->getFile()."[".$Exception->getLine()."]";
104  ?><table width="100%" cellpadding="5"
105  style="border: 2px solid #666666; background: #CCCCCC;
106  font-family: Courier New, Courier, monospace;
107  margin-top: 10px;"><tr><td>
108  <div style="color: #666666;">
109  <span style="font-size: 150%;">
110  <b>Uncaught Exception</b></span><br />
111  <b>Message:</b> <i><?PHP print $Exception->getMessage(); ?></i><br />
112  <b>Location:</b> <i><?PHP print $Location; ?></i><br />
113  <b>Trace:</b>
114  <blockquote><pre><?PHP print $Exception->getTraceAsString();
115  ?></pre></blockquote>
116  </div>
117  </td></tr></table><?PHP
118 
119  # log exception if possible
120  $LogMsg = "Uncaught exception (".$Exception->getMessage().").";
121  $this->LogError(self::LOGLVL_ERROR, $LogMsg);
122  }
136  static function AddObjectDirectory(
137  $Dir, $Prefix = "", $ClassPattern = NULL, $ClassReplacement = NULL)
138  {
139  # make sure directory has trailing slash
140  $Dir = $Dir.((substr($Dir, -1) != "/") ? "/" : "");
141 
142  # add directory to directory list
143  self::$ObjectDirectories = array_merge(
144  array($Dir => array(
145  "Prefix" => $Prefix,
146  "ClassPattern" => $ClassPattern,
147  "ClassReplacement" => $ClassReplacement,
148  )),
149  self::$ObjectDirectories);
150  }
151 
171  function AddImageDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
172  {
173  # add directories to existing image directory list
174  $this->ImageDirList = $this->AddToDirList(
175  $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
176  }
177 
198  function AddIncludeDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
199  {
200  # add directories to existing image directory list
201  $this->IncludeDirList = $this->AddToDirList(
202  $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
203  }
204 
224  function AddInterfaceDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
225  {
226  # add directories to existing image directory list
227  $this->InterfaceDirList = $this->AddToDirList(
228  $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
229  }
230 
250  function AddFunctionDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
251  {
252  # add directories to existing image directory list
253  $this->FunctionDirList = $this->AddToDirList(
254  $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
255  }
256 
262  function SetBrowserDetectionFunc($DetectionFunc)
263  {
264  $this->BrowserDetectFunc = $DetectionFunc;
265  }
266 
273  function AddUnbufferedCallback($Callback, $Parameters=array())
274  {
275  if (is_callable($Callback))
276  {
277  $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
278  }
279  }
280 
287  function TemplateLocationCacheExpirationInterval($NewInterval = -1)
288  {
289  if ($NewInterval >= 0)
290  {
291  $this->Settings["TemplateLocationCacheInterval"] = $NewInterval;
292  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
293  ." SET TemplateLocationCacheInterval = '"
294  .intval($NewInterval)."'");
295  }
296  return $this->Settings["TemplateLocationCacheInterval"];
297  }
298 
304  static function SessionLifetime($NewValue = NULL)
305  {
306  if ($NewValue !== NULL)
307  {
308  self::$SessionLifetime = $NewValue;
309  }
310  return self::$SessionLifetime;
311  }
312 
317  function LoadPage($PageName)
318  {
319  # sanitize incoming page name and save local copy
320  $PageName = preg_replace("/[^a-zA-Z0-9_.-]/", "", $PageName);
321  $this->PageName = $PageName;
322 
323  # buffer any output from includes or PHP file
324  ob_start();
325 
326  # include any files needed to set up execution environment
327  foreach ($this->EnvIncludes as $IncludeFile)
328  {
329  include($IncludeFile);
330  }
331 
332  # signal page load
333  $this->SignalEvent("EVENT_PAGE_LOAD", array("PageName" => $PageName));
334 
335  # signal PHP file load
336  $SignalResult = $this->SignalEvent("EVENT_PHP_FILE_LOAD", array(
337  "PageName" => $PageName));
338 
339  # if signal handler returned new page name value
340  $NewPageName = $PageName;
341  if (($SignalResult["PageName"] != $PageName)
342  && strlen($SignalResult["PageName"]))
343  {
344  # if new page name value is page file
345  if (file_exists($SignalResult["PageName"]))
346  {
347  # use new value for PHP file name
348  $PageFile = $SignalResult["PageName"];
349  }
350  else
351  {
352  # use new value for page name
353  $NewPageName = $SignalResult["PageName"];
354  }
355  }
356 
357  # if we do not already have a PHP file
358  if (!isset($PageFile))
359  {
360  # look for PHP file for page
361  $OurPageFile = "pages/".$NewPageName.".php";
362  $LocalPageFile = "local/pages/".$NewPageName.".php";
363  $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
364  : (file_exists($OurPageFile) ? $OurPageFile
365  : "pages/".$this->DefaultPage.".php");
366  }
367 
368  # load PHP file
369  include($PageFile);
370 
371  # save buffered output to be displayed later after HTML file loads
372  $PageOutput = ob_get_contents();
373  ob_end_clean();
374 
375  # signal PHP file load is complete
376  ob_start();
377  $Context["Variables"] = get_defined_vars();
378  $this->SignalEvent("EVENT_PHP_FILE_LOAD_COMPLETE",
379  array("PageName" => $PageName, "Context" => $Context));
380  $PageCompleteOutput = ob_get_contents();
381  ob_end_clean();
382 
383  # set up for possible TSR (Terminate and Stay Resident :))
384  $ShouldTSR = $this->PrepForTSR();
385 
386  # if PHP file indicated we should autorefresh to somewhere else
387  if ($this->JumpToPage)
388  {
389  if (!strlen(trim($PageOutput)))
390  {
391  ?><html>
392  <head>
393  <meta http-equiv="refresh" content="0; URL=<?PHP
394  print($this->JumpToPage); ?>">
395  </head>
396  <body bgcolor="white">
397  </body>
398  </html><?PHP
399  }
400  }
401  # else if HTML loading is not suppressed
402  elseif (!$this->SuppressHTML)
403  {
404  # set content-type to get rid of diacritic errors
405  header("Content-Type: text/html; charset="
406  .$this->HtmlCharset, TRUE);
407 
408  # load common HTML file (defines common functions) if available
409  $CommonHtmlFile = $this->FindFile($this->IncludeDirList,
410  "Common", array("tpl", "html"));
411  if ($CommonHtmlFile) { include($CommonHtmlFile); }
412 
413  # load UI functions
414  $this->LoadUIFunctions();
415 
416  # begin buffering content
417  ob_start();
418 
419  # signal HTML file load
420  $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD", array(
421  "PageName" => $PageName));
422 
423  # if signal handler returned new page name value
424  $NewPageName = $PageName;
425  $PageContentFile = NULL;
426  if (($SignalResult["PageName"] != $PageName)
427  && strlen($SignalResult["PageName"]))
428  {
429  # if new page name value is HTML file
430  if (file_exists($SignalResult["PageName"]))
431  {
432  # use new value for HTML file name
433  $PageContentFile = $SignalResult["PageName"];
434  }
435  else
436  {
437  # use new value for page name
438  $NewPageName = $SignalResult["PageName"];
439  }
440  }
441 
442  # load page content HTML file if available
443  if ($PageContentFile === NULL)
444  {
445  $PageContentFile = $this->FindFile(
446  $this->InterfaceDirList, $NewPageName,
447  array("tpl", "html"));
448  }
449  if ($PageContentFile)
450  {
451  include($PageContentFile);
452  }
453  else
454  {
455  print "<h2>ERROR: No HTML/TPL template found"
456  ." for this page.</h2>";
457  }
458 
459  # signal HTML file load complete
460  $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD_COMPLETE");
461 
462  # stop buffering and save output
463  $PageContentOutput = ob_get_contents();
464  ob_end_clean();
465 
466  # load page start HTML file if available
467  ob_start();
468  $PageStartFile = $this->FindFile($this->IncludeDirList, "Start",
469  array("tpl", "html"), array("StdPage", "StandardPage"));
470  if ($PageStartFile) { include($PageStartFile); }
471  $PageStartOutput = ob_get_contents();
472  ob_end_clean();
473 
474  # load page end HTML file if available
475  ob_start();
476  $PageEndFile = $this->FindFile($this->IncludeDirList, "End",
477  array("tpl", "html"), array("StdPage", "StandardPage"));
478  if ($PageEndFile) { include($PageEndFile); }
479  $PageEndOutput = ob_get_contents();
480  ob_end_clean();
481 
482  # get list of any required files not loaded
483  $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
484 
485  # if a browser detection function has been made available
486  if (is_callable($this->BrowserDetectFunc))
487  {
488  # call function to get browser list
489  $Browsers = call_user_func($this->BrowserDetectFunc);
490 
491  # for each required file
492  $NewRequiredFiles = array();
493  foreach ($RequiredFiles as $File)
494  {
495  # if file name includes browser keyword
496  if (preg_match("/%BROWSER%/", $File))
497  {
498  # for each browser
499  foreach ($Browsers as $Browser)
500  {
501  # substitute in browser name and add to new file list
502  $NewRequiredFiles[] = preg_replace(
503  "/%BROWSER%/", $Browser, $File);
504  }
505  }
506  else
507  {
508  # add to new file list
509  $NewRequiredFiles[] = $File;
510  }
511  }
512  $RequiredFiles = $NewRequiredFiles;
513  }
514 
515  # for each required file
516  foreach ($RequiredFiles as $File)
517  {
518  # locate specific file to use
519  $FilePath = $this->GUIFile($File);
520 
521  # if file was found
522  if ($FilePath)
523  {
524  # determine file type
525  $NamePieces = explode(".", $File);
526  $FileSuffix = strtolower(array_pop($NamePieces));
527 
528  # add file to HTML output based on file type
529  $FilePath = htmlspecialchars($FilePath);
530  switch ($FileSuffix)
531  {
532  case "js":
533  $Tag = '<script type="text/javascript" src="'
534  .$FilePath.'"></script>';
535  $PageEndOutput = preg_replace(
536  "#</body>#i", $Tag."\n</body>", $PageEndOutput, 1);
537  break;
538 
539  case "css":
540  $Tag = '<link rel="stylesheet" type="text/css"'
541  .' media="all" href="'.$FilePath.'">';
542  $PageStartOutput = preg_replace(
543  "#</head>#i", $Tag."\n</head>", $PageStartOutput, 1);
544  break;
545  }
546  }
547  }
548 
549  # write out page
550  print $PageStartOutput.$PageContentOutput.$PageEndOutput;
551  }
552 
553  # run any post-processing routines
554  foreach ($this->PostProcessingFuncs as $Func)
555  {
556  call_user_func_array($Func["FunctionName"], $Func["Arguments"]);
557  }
558 
559  # write out any output buffered from page code execution
560  if (strlen($PageOutput))
561  {
562  if (!$this->SuppressHTML)
563  {
564  ?><table width="100%" cellpadding="5"
565  style="border: 2px solid #666666; background: #CCCCCC;
566  font-family: Courier New, Courier, monospace;
567  margin-top: 10px;"><tr><td><?PHP
568  }
569  if ($this->JumpToPage)
570  {
571  ?><div style="color: #666666;"><span style="font-size: 150%;">
572  <b>Page Jump Aborted</b></span>
573  (because of error or other unexpected output)<br />
574  <b>Jump Target:</b>
575  <i><?PHP print($this->JumpToPage); ?></i></div><?PHP
576  }
577  print $PageOutput;
578  if (!$this->SuppressHTML)
579  {
580  ?></td></tr></table><?PHP
581  }
582  }
583 
584  # write out any output buffered from the page code execution complete signal
585  if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
586  {
587  print $PageCompleteOutput;
588  }
589 
590  # execute callbacks that should not have their output buffered
591  foreach ($this->UnbufferedCallbacks as $Callback)
592  {
593  call_user_func_array($Callback[0], $Callback[1]);
594  }
595 
596  # terminate and stay resident (TSR!) if indicated and HTML has been output
597  # (only TSR if HTML has been output because otherwise browsers will misbehave)
598  if ($ShouldTSR) { $this->LaunchTSR(); }
599  }
600 
606  function GetPageName()
607  {
608  return $this->PageName;
609  }
610 
617  function SetJumpToPage($Page)
618  {
619  if (!is_null($Page) && (strpos($Page, "?") === FALSE)
620  && ((strpos($Page, "=") !== FALSE)
621  || ((stripos($Page, ".php") === FALSE)
622  && (stripos($Page, ".htm") === FALSE)
623  && (strpos($Page, "/") === FALSE)))
624  && (stripos($Page, "http://") !== 0)
625  && (stripos($Page, "https://") !== 0))
626  {
627  $this->JumpToPage = "index.php?P=".$Page;
628  }
629  else
630  {
631  $this->JumpToPage = $Page;
632  }
633  }
634 
639  function JumpToPageIsSet()
640  {
641  return ($this->JumpToPage === NULL) ? FALSE : TRUE;
642  }
643 
653  function HtmlCharset($NewSetting = NULL)
654  {
655  if ($NewSetting !== NULL) { $this->HtmlCharset = $NewSetting; }
656  return $this->HtmlCharset;
657  }
658 
665  function SuppressHTMLOutput($NewSetting = TRUE)
666  {
667  $this->SuppressHTML = $NewSetting;
668  }
669 
676  function ActiveUserInterface($UIName = NULL)
677  {
678  if ($UIName !== NULL)
679  {
680  $this->ActiveUI = preg_replace("/^SPTUI--/", "", $UIName);
681  }
682  return $this->ActiveUI;
683  }
684 
690  function GetUserInterfaces()
691  {
692  # possible UI directories
693  $InterfaceDirs = array(
694  "interface",
695  "local/interface");
696 
697  # start out with an empty list
698  $Interfaces = array();
699 
700  # for each possible UI directory
701  foreach ($InterfaceDirs as $InterfaceDir)
702  {
703  $Dir = dir($InterfaceDir);
704 
705  # for each file in current directory
706  while (($DirEntry = $Dir->read()) !== FALSE)
707  {
708  $InterfacePath = $InterfaceDir."/".$DirEntry;
709 
710  # skip anything that doesn't have a name in the required format
711  if (!preg_match('/^[a-zA-Z]+$/', $DirEntry))
712  {
713  continue;
714  }
715 
716  # skip anything that isn't a directory
717  if (!is_dir($InterfacePath))
718  {
719  continue;
720  }
721 
722  # read the UI name (if available)
723  $UIName = @file_get_contents($InterfacePath."/NAME");
724 
725  # use the directory name if the UI name isn't available
726  if ($UIName === FALSE || !strlen($UIName))
727  {
728  $UIName = $DirEntry;
729  }
730 
731  $Interfaces[$InterfacePath] = $UIName;
732  }
733 
734  $Dir->close();
735  }
736 
737  # return list to caller
738  return $Interfaces;
739  }
740 
756  function AddPostProcessingCall($FunctionName,
757  &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
758  &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
759  &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
760  {
761  $FuncIndex = count($this->PostProcessingFuncs);
762  $this->PostProcessingFuncs[$FuncIndex]["FunctionName"] = $FunctionName;
763  $this->PostProcessingFuncs[$FuncIndex]["Arguments"] = array();
764  $Index = 1;
765  while (isset(${"Arg".$Index}) && (${"Arg".$Index} !== self::NOVALUE))
766  {
767  $this->PostProcessingFuncs[$FuncIndex]["Arguments"][$Index]
768  =& ${"Arg".$Index};
769  $Index++;
770  }
771  }
772 
778  function AddEnvInclude($FileName)
779  {
780  $this->EnvIncludes[] = $FileName;
781  }
782 
789  function GUIFile($FileName)
790  {
791  # determine which location to search based on file suffix
792  $FileIsImage = preg_match("/\.(gif|jpg|png)$/", $FileName);
793  $DirList = $FileIsImage ? $this->ImageDirList : $this->IncludeDirList;
794 
795  # search for file
796  $FoundFileName = $this->FindFile($DirList, $FileName);
797 
798  # add non-image files to list of found files (used for required files loading)
799  if (!$FileIsImage) { $this->FoundUIFiles[] = basename($FoundFileName); }
800 
801  # return file name to caller
802  return $FoundFileName;
803  }
804 
814  function PUIFile($FileName)
815  {
816  $FullFileName = $this->GUIFile($FileName);
817  if ($FullFileName) { print($FullFileName); }
818  }
819 
827  function RequireUIFile($FileName)
828  {
829  $this->AdditionalRequiredUIFiles[] = $FileName;
830  }
831 
840  function LoadFunction($Callback)
841  {
842  # if specified function is not currently available
843  if (!is_callable($Callback))
844  {
845  # if function info looks legal
846  if (is_string($Callback) && strlen($Callback))
847  {
848  # start with function directory list
849  $Locations = $this->FunctionDirList;
850 
851  # add object directories to list
852  $Locations = array_merge(
853  $Locations, array_keys(self::$ObjectDirectories));
854 
855  # look for function file
856  $FunctionFileName = $this->FindFile($Locations, "F-".$Callback,
857  array("php", "html"));
858 
859  # if function file was found
860  if ($FunctionFileName)
861  {
862  # load function file
863  include_once($FunctionFileName);
864  }
865  else
866  {
867  # log error indicating function load failed
868  $this->LogError(self::LOGLVL_ERROR, "Unable to load function"
869  ." for callback \"".$Callback."\".");
870  }
871  }
872  else
873  {
874  # log error indicating specified function info was bad
875  $this->LogError(self::LOGLVL_ERROR, "Unloadable callback value"
876  ." (".$Callback.")"
877  ." passed to AF::LoadFunction().");
878  }
879  }
880 
881  # report to caller whether function load succeeded
882  return is_callable($Callback);
883  }
884 
890  {
891  return microtime(TRUE) - $this->ExecutionStartTime;
892  }
893 
899  {
900  return ini_get("max_execution_time") - $this->GetElapsedExecutionTime();
901  }
902 
907  function HtaccessSupport()
908  {
909  # HTACCESS_SUPPORT is set in the .htaccess file
910  return isset($_SERVER["HTACCESS_SUPPORT"]);
911  }
912 
924  function LogError($Level, $Msg)
925  {
926  # if error level is at or below current logging level
927  if ($this->Settings["LoggingLevel"] >= $Level)
928  {
929  # attempt to log error message
930  $Result = $this->LogMessage($Level, $Msg);
931 
932  # if logging attempt failed and level indicated significant error
933  if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
934  {
935  # throw exception about inability to log error
936  static $AlreadyThrewException = FALSE;
937  if (!$AlreadyThrewException)
938  {
939  $AlreadyThrewException = TRUE;
940  throw new Exception("Unable to log error (".$Level.": ".$Msg.").");
941  }
942  }
943 
944  # report to caller whether message was logged
945  return $Result;
946  }
947  else
948  {
949  # report to caller that message was not logged
950  return FALSE;
951  }
952  }
953 
965  function LogMessage($Level, $Msg)
966  {
967  # if message level is at or below current logging level
968  if ($this->Settings["LoggingLevel"] >= $Level)
969  {
970  # attempt to open log file
971  $FHndl = @fopen("local/logs/cwis.log", "a");
972 
973  # if log file could not be open
974  if ($FHndl === FALSE)
975  {
976  # report to caller that message was not logged
977  return FALSE;
978  }
979  else
980  {
981  # format log entry
982  $ErrorAbbrevs = array(
983  self::LOGLVL_FATAL => "FTL",
984  self::LOGLVL_ERROR => "ERR",
985  self::LOGLVL_WARNING => "WRN",
986  self::LOGLVL_INFO => "INF",
987  self::LOGLVL_DEBUG => "DBG",
988  self::LOGLVL_TRACE => "TRC",
989  );
990  $LogEntry = date("Y-m-d H:i:s")
991  ." ".($this->RunningInBackground ? "B" : "F")
992  ." ".$ErrorAbbrevs[$Level]
993  ." ".$Msg;
994 
995  # write entry to log
996  $Success = fputs($FHndl, $LogEntry."\n");
997 
998  # close log file
999  fclose($FHndl);
1000 
1001  # report to caller whether message was logged
1002  return ($Success === FALSE) ? FALSE : TRUE;
1003  }
1004  }
1005  else
1006  {
1007  # report to caller that message was not logged
1008  return FALSE;
1009  }
1010  }
1011 
1033  function LoggingLevel($NewValue = NULL)
1034  {
1035  # if new logging level was specified
1036  if ($NewValue !== NULL)
1037  {
1038  # constrain new level to within legal bounds and store locally
1039  $this->Settings["LoggingLevel"] = max(min($NewValue, 6), 1);
1040 
1041  # save new logging level in database
1042  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
1043  ." SET LoggingLevel = "
1044  .intval($this->Settings["LoggingLevel"]));
1045  }
1046 
1047  # report current logging level to caller
1048  return $this->Settings["LoggingLevel"];
1049  }
1050 
1055  const LOGLVL_TRACE = 6;
1060  const LOGLVL_DEBUG = 5;
1066  const LOGLVL_INFO = 4;
1071  const LOGLVL_WARNING = 3;
1077  const LOGLVL_ERROR = 2;
1082  const LOGLVL_FATAL = 1;
1083 
1084  /*@)*/ /* Application Framework */
1085 
1086  # ---- Event Handling ----------------------------------------------------
1087  /*@(*/
1089 
1099  const EVENTTYPE_CHAIN = 2;
1105  const EVENTTYPE_FIRST = 3;
1113  const EVENTTYPE_NAMED = 4;
1114 
1116  const ORDER_FIRST = 1;
1118  const ORDER_MIDDLE = 2;
1120  const ORDER_LAST = 3;
1121 
1130  function RegisterEvent($EventsOrEventName, $EventType = NULL)
1131  {
1132  # convert parameters to array if not already in that form
1133  $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1134  : array($EventsOrEventName => $Type);
1135 
1136  # for each event
1137  foreach ($Events as $Name => $Type)
1138  {
1139  # store event information
1140  $this->RegisteredEvents[$Name]["Type"] = $Type;
1141  $this->RegisteredEvents[$Name]["Hooks"] = array();
1142  }
1143  }
1144 
1150  function IsRegisteredEvent($EventName)
1151  {
1152  return array_key_exists($EventName, $this->RegisteredEvents)
1153  ? TRUE : FALSE;
1154  }
1155 
1169  function HookEvent($EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
1170  {
1171  # convert parameters to array if not already in that form
1172  $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1173  : array($EventsOrEventName => $Callback);
1174 
1175  # for each event
1176  $Success = TRUE;
1177  foreach ($Events as $EventName => $EventCallback)
1178  {
1179  # if callback is valid
1180  if (is_callable($EventCallback))
1181  {
1182  # if this is a periodic event we process internally
1183  if (isset($this->PeriodicEvents[$EventName]))
1184  {
1185  # process event now
1186  $this->ProcessPeriodicEvent($EventName, $EventCallback);
1187  }
1188  # if specified event has been registered
1189  elseif (isset($this->RegisteredEvents[$EventName]))
1190  {
1191  # add callback for event
1192  $this->RegisteredEvents[$EventName]["Hooks"][]
1193  = array("Callback" => $EventCallback, "Order" => $Order);
1194 
1195  # sort callbacks by order
1196  if (count($this->RegisteredEvents[$EventName]["Hooks"]) > 1)
1197  {
1198  usort($this->RegisteredEvents[$EventName]["Hooks"],
1199  array("ApplicationFramework", "HookEvent_OrderCompare"));
1200  }
1201  }
1202  else
1203  {
1204  $Success = FALSE;
1205  }
1206  }
1207  else
1208  {
1209  $Success = FALSE;
1210  }
1211  }
1212 
1213  # report to caller whether all callbacks were hooked
1214  return $Success;
1215  }
1216  private static function HookEvent_OrderCompare($A, $B)
1217  {
1218  if ($A["Order"] == $B["Order"]) { return 0; }
1219  return ($A["Order"] < $B["Order"]) ? -1 : 1;
1220  }
1221 
1230  function SignalEvent($EventName, $Parameters = NULL)
1231  {
1232  $ReturnValue = NULL;
1233 
1234  # if event has been registered
1235  if (isset($this->RegisteredEvents[$EventName]))
1236  {
1237  # set up default return value (if not NULL)
1238  switch ($this->RegisteredEvents[$EventName]["Type"])
1239  {
1240  case self::EVENTTYPE_CHAIN:
1241  $ReturnValue = $Parameters;
1242  break;
1243 
1244  case self::EVENTTYPE_NAMED:
1245  $ReturnValue = array();
1246  break;
1247  }
1248 
1249  # for each callback for this event
1250  foreach ($this->RegisteredEvents[$EventName]["Hooks"] as $Hook)
1251  {
1252  # invoke callback
1253  $Callback = $Hook["Callback"];
1254  $Result = ($Parameters !== NULL)
1255  ? call_user_func_array($Callback, $Parameters)
1256  : call_user_func($Callback);
1257 
1258  # process return value based on event type
1259  switch ($this->RegisteredEvents[$EventName]["Type"])
1260  {
1261  case self::EVENTTYPE_CHAIN:
1262  if ($Result !== NULL)
1263  {
1264  foreach ($Parameters as $Index => $Value)
1265  {
1266  if (array_key_exists($Index, $Result))
1267  {
1268  $Parameters[$Index] = $Result[$Index];
1269  }
1270  }
1271  $ReturnValue = $Parameters;
1272  }
1273  break;
1274 
1275  case self::EVENTTYPE_FIRST:
1276  if ($Result !== NULL)
1277  {
1278  $ReturnValue = $Result;
1279  break 2;
1280  }
1281  break;
1282 
1283  case self::EVENTTYPE_NAMED:
1284  $CallbackName = is_array($Callback)
1285  ? (is_object($Callback[0])
1286  ? get_class($Callback[0])
1287  : $Callback[0])."::".$Callback[1]
1288  : $Callback;
1289  $ReturnValue[$CallbackName] = $Result;
1290  break;
1291 
1292  default:
1293  break;
1294  }
1295  }
1296  }
1297  else
1298  {
1299  $this->LogError(self::LOGLVL_WARNING,
1300  "Unregistered event signaled (".$EventName.").");
1301  }
1302 
1303  # return value if any to caller
1304  return $ReturnValue;
1305  }
1306 
1312  function IsStaticOnlyEvent($EventName)
1313  {
1314  return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
1315  }
1316 
1317  /*@)*/ /* Event Handling */
1318 
1319  # ---- Task Management ---------------------------------------------------
1320  /*@(*/
1322 
1324  const PRIORITY_HIGH = 1;
1326  const PRIORITY_MEDIUM = 2;
1328  const PRIORITY_LOW = 3;
1331 
1344  function QueueTask($Callback, $Parameters = NULL,
1345  $Priority = self::PRIORITY_MEDIUM, $Description = "")
1346  {
1347  # pack task info and write to database
1348  if ($Parameters === NULL) { $Parameters = array(); }
1349  $this->DB->Query("INSERT INTO TaskQueue"
1350  ." (Callback, Parameters, Priority, Description)"
1351  ." VALUES ('".addslashes(serialize($Callback))."', '"
1352  .addslashes(serialize($Parameters))."', ".intval($Priority).", '"
1353  .addslashes($Description)."')");
1354  }
1355 
1373  function QueueUniqueTask($Callback, $Parameters = NULL,
1374  $Priority = self::PRIORITY_MEDIUM, $Description = "")
1375  {
1376  if ($this->TaskIsInQueue($Callback, $Parameters))
1377  {
1378  $QueryResult = $this->DB->Query("SELECT TaskId,Priority FROM TaskQueue"
1379  ." WHERE Callback = '".addslashes(serialize($Callback))."'"
1380  .($Parameters ? " AND Parameters = '"
1381  .addslashes(serialize($Parameters))."'" : ""));
1382  if ($QueryResult !== FALSE)
1383  {
1384  $Record = $this->DB->FetchRow();
1385  if ($Record["Priority"] > $Priority)
1386  {
1387  $this->DB->Query("UPDATE TaskQueue"
1388  ." SET Priority = ".intval($Priority)
1389  ." WHERE TaskId = ".intval($Record["TaskId"]));
1390  }
1391  }
1392  return FALSE;
1393  }
1394  else
1395  {
1396  $this->QueueTask($Callback, $Parameters, $Priority, $Description);
1397  return TRUE;
1398  }
1399  }
1400 
1410  function TaskIsInQueue($Callback, $Parameters = NULL)
1411  {
1412  $FoundCount = $this->DB->Query("SELECT COUNT(*) AS FoundCount FROM TaskQueue"
1413  ." WHERE Callback = '".addslashes(serialize($Callback))."'"
1414  .($Parameters ? " AND Parameters = '"
1415  .addslashes(serialize($Parameters))."'" : ""),
1416  "FoundCount")
1417  + $this->DB->Query("SELECT COUNT(*) AS FoundCount FROM RunningTasks"
1418  ." WHERE Callback = '".addslashes(serialize($Callback))."'"
1419  .($Parameters ? " AND Parameters = '"
1420  .addslashes(serialize($Parameters))."'" : ""),
1421  "FoundCount");
1422  return ($FoundCount ? TRUE : FALSE);
1423  }
1424 
1430  function GetTaskQueueSize($Priority = NULL)
1431  {
1432  return $this->DB->Query("SELECT COUNT(*) AS QueueSize FROM TaskQueue"
1433  .($Priority ? " WHERE Priority = ".intval($Priority) : ""),
1434  "QueueSize");
1435  }
1436 
1444  function GetQueuedTaskList($Count = 100, $Offset = 0)
1445  {
1446  return $this->GetTaskList("SELECT * FROM TaskQueue"
1447  ." ORDER BY Priority, TaskId ", $Count, $Offset);
1448  }
1449 
1457  function GetRunningTaskList($Count = 100, $Offset = 0)
1458  {
1459  return $this->GetTaskList("SELECT * FROM RunningTasks"
1460  ." WHERE StartedAt >= '".date("Y-m-d H:i:s",
1461  (time() - ini_get("max_execution_time")))."'"
1462  ." ORDER BY StartedAt", $Count, $Offset);
1463  }
1464 
1472  function GetOrphanedTaskList($Count = 100, $Offset = 0)
1473  {
1474  return $this->GetTaskList("SELECT * FROM RunningTasks"
1475  ." WHERE StartedAt < '".date("Y-m-d H:i:s",
1476  (time() - ini_get("max_execution_time")))."'"
1477  ." ORDER BY StartedAt", $Count, $Offset);
1478  }
1479 
1485  {
1486  return $this->DB->Query("SELECT COUNT(*) AS Count FROM RunningTasks"
1487  ." WHERE StartedAt < '".date("Y-m-d H:i:s",
1488  (time() - ini_get("max_execution_time")))."'",
1489  "Count");
1490  }
1491 
1497  function ReQueueOrphanedTask($TaskId, $NewPriority = NULL)
1498  {
1499  $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
1500  $this->DB->Query("INSERT INTO TaskQueue"
1501  ." (Callback,Parameters,Priority,Description) "
1502  ."SELECT Callback, Parameters, Priority, Description"
1503  ." FROM RunningTasks WHERE TaskId = ".intval($TaskId));
1504  if ($NewPriority !== NULL)
1505  {
1506  $NewTaskId = $this->DB->LastInsertId("TaskQueue");
1507  $this->DB->Query("UPDATE TaskQueue SET Priority = "
1508  .intval($NewPriority)
1509  ." WHERE TaskId = ".intval($NewTaskId));
1510  }
1511  $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
1512  $this->DB->Query("UNLOCK TABLES");
1513  }
1514 
1519  function DeleteTask($TaskId)
1520  {
1521  $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
1522  $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
1523  }
1524 
1532  function GetTask($TaskId)
1533  {
1534  # assume task will not be found
1535  $Task = NULL;
1536 
1537  # look for task in task queue
1538  $this->DB->Query("SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
1539 
1540  # if task was not found in queue
1541  if (!$this->DB->NumRowsSelected())
1542  {
1543  # look for task in running task list
1544  $this->DB->Query("SELECT * FROM RunningTasks WHERE TaskId = "
1545  .intval($TaskId));
1546  }
1547 
1548  # if task was found
1549  if ($this->DB->NumRowsSelected())
1550  {
1551  # if task was periodic
1552  $Row = $this->DB->FetchRow();
1553  if ($Row["Callback"] ==
1554  serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
1555  {
1556  # unpack periodic task callback
1557  $WrappedCallback = unserialize($Row["Parameters"]);
1558  $Task["Callback"] = $WrappedCallback[1];
1559  $Task["Parameters"] = $WrappedCallback[2];
1560  }
1561  else
1562  {
1563  # unpack task callback and parameters
1564  $Task["Callback"] = unserialize($Row["Callback"]);
1565  $Task["Parameters"] = unserialize($Row["Parameters"]);
1566  }
1567  }
1568 
1569  # return task to caller
1570  return $Task;
1571  }
1572 
1580  function TaskExecutionEnabled($NewValue = NULL)
1581  {
1582  if ($NewValue !== NULL)
1583  {
1584  $this->Settings["TaskExecutionEnabled"] = $NewValue ? 1 : 0;
1585  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
1586  ." SET TaskExecutionEnabled = "
1587  .$this->Settings["TaskExecutionEnabled"]);
1588  }
1589  return $this->Settings["TaskExecutionEnabled"];
1590  }
1591 
1597  function MaxTasks($NewValue = NULL)
1598  {
1599  if (func_num_args() && ($NewValue >= 1))
1600  {
1601  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
1602  ." SET MaxTasksRunning = ".intval($NewValue));
1603  $this->Settings["MaxTasksRunning"] = intval($NewValue);
1604  }
1605  return $this->Settings["MaxTasksRunning"];
1606  }
1607 
1615  function MaxExecutionTime($NewValue = NULL)
1616  {
1617  if (func_num_args() && !ini_get("safe_mode"))
1618  {
1619  if ($NewValue != $this->Settings["MaxExecTime"])
1620  {
1621  $this->Settings["MaxExecTime"] = max($NewValue, 5);
1622  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
1623  ." SET MaxExecTime = '"
1624  .intval($this->Settings["MaxExecTime"])."'");
1625  }
1626  ini_set("max_execution_time", $this->Settings["MaxExecTime"]);
1627  set_time_limit($this->Settings["MaxExecTime"]);
1628  }
1629  return ini_get("max_execution_time");
1630  }
1631 
1632  /*@)*/ /* Task Management */
1633 
1634  # ---- Backward Compatibility --------------------------------------------
1635 
1640  function FindCommonTemplate($BaseName)
1641  {
1642  return $this->FindFile(
1643  $this->IncludeDirList, $BaseName, array("tpl", "html"));
1644  }
1645  /*@(*/
1647 
1648 
1649  # ---- PRIVATE INTERFACE -------------------------------------------------
1650 
1651  private $ActiveUI = "default";
1652  private $BrowserDetectFunc;
1653  private $DB;
1654  private $DefaultPage = "Home";
1655  private $EnvIncludes = array();
1656  private $ExecutionStartTime;
1657  private $FoundUIFiles = array();
1658  private $AdditionalRequiredUIFiles = array();
1659  private $HtmlCharset = "UTF-8";
1660  private $JumpToPage = NULL;
1661  private $PageName;
1662  private $MaxRunningTasksToTrack = 250;
1663  private $PostProcessingFuncs = array();
1664  private $RunningInBackground = FALSE;
1665  private $RunningTask;
1666  private $Settings;
1667  private $SuppressHTML = FALSE;
1668  private $SaveTemplateLocationCache = FALSE;
1669  private $UnbufferedCallbacks = array();
1670 
1671  private static $AppName = "ScoutAF";
1672  private static $ObjectDirectories = array();
1673  private static $SessionLifetime = 1440;
1674 
1679  private $NoTSR = FALSE;
1680 
1681  private $PeriodicEvents = array(
1682  "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
1683  "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
1684  "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
1685  "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
1686  "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
1687  );
1688  private $UIEvents = array(
1689  "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
1690  "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
1691  "EVENT_PHP_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
1692  "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
1693  "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
1694  );
1695 
1699  private function LoadSettings()
1700  {
1701  # read settings in from database
1702  $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
1703  $this->Settings = $this->DB->FetchRow();
1704 
1705  # if settings were not previously initialized
1706  if (!$this->Settings)
1707  {
1708  # initialize settings in database
1709  $this->DB->Query("INSERT INTO ApplicationFrameworkSettings"
1710  ." (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
1711 
1712  # read new settings in from database
1713  $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
1714  $this->Settings = $this->DB->FetchRow();
1715  }
1716 
1717  # if template location cache has been saved to database
1718  if (isset($this->Settings["TemplateLocationCache"]))
1719  {
1720  # unserialize cache values into array and use if valid
1721  $Cache = unserialize($this->Settings["TemplateLocationCache"]);
1722  $this->Settings["TemplateLocationCache"] =
1723  count($Cache) ? $Cache : array();
1724  }
1725  else
1726  {
1727  # start with empty cache
1728  $this->Settings["TemplateLocationCache"] = array();
1729  }
1730  }
1731 
1748  private function FindFile($DirectoryList, $BaseName,
1749  $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
1750  {
1751  # generate template cache index for this page
1752  $CacheIndex = md5(serialize($DirectoryList))
1753  .":".$this->ActiveUI.":".$BaseName;
1754 
1755  # if we have cached location and cache expiration time has not elapsed
1756  if (($this->Settings["TemplateLocationCacheInterval"] > 0)
1757  && count($this->Settings["TemplateLocationCache"])
1758  && array_key_exists($CacheIndex,
1759  $this->Settings["TemplateLocationCache"])
1760  && (time() < strtotime(
1761  $this->Settings["TemplateLocationCacheExpiration"])))
1762  {
1763  # use template location from cache
1764  $FoundFileName = $this->Settings[
1765  "TemplateLocationCache"][$CacheIndex];
1766  }
1767  else
1768  {
1769  # if suffixes specified and base name does not include suffix
1770  if (count($PossibleSuffixes)
1771  && !preg_match("/\.[a-zA-Z0-9]+$/", $BaseName))
1772  {
1773  # add versions of file names with suffixes to file name list
1774  $FileNames = array();
1775  foreach ($PossibleSuffixes as $Suffix)
1776  {
1777  $FileNames[] = $BaseName.".".$Suffix;
1778  }
1779  }
1780  else
1781  {
1782  # use base name as file name
1783  $FileNames = array($BaseName);
1784  }
1785 
1786  # if prefixes specified
1787  if (count($PossiblePrefixes))
1788  {
1789  # add versions of file names with prefixes to file name list
1790  $NewFileNames = array();
1791  foreach ($FileNames as $FileName)
1792  {
1793  foreach ($PossiblePrefixes as $Prefix)
1794  {
1795  $NewFileNames[] = $Prefix.$FileName;
1796  }
1797  }
1798  $FileNames = $NewFileNames;
1799  }
1800 
1801  # for each possible location
1802  $FoundFileName = NULL;
1803  foreach ($DirectoryList as $Dir)
1804  {
1805  # substitute active UI name into path
1806  $Dir = str_replace("%ACTIVEUI%", $this->ActiveUI, $Dir);
1807 
1808  # for each possible file name
1809  foreach ($FileNames as $File)
1810  {
1811  # if template is found at location
1812  if (file_exists($Dir.$File))
1813  {
1814  # save full template file name and stop looking
1815  $FoundFileName = $Dir.$File;
1816  break 2;
1817  }
1818  }
1819  }
1820 
1821  # save location in cache
1822  $this->Settings["TemplateLocationCache"][$CacheIndex]
1823  = $FoundFileName;
1824 
1825  # set flag indicating that cache should be saved
1826  $this->SaveTemplateLocationCache = TRUE;
1827  }
1828 
1829  # return full template file name to caller
1830  return $FoundFileName;
1831  }
1832 
1839  private function GetRequiredFilesNotYetLoaded($PageContentFile)
1840  {
1841  # start out assuming no files required
1842  $RequiredFiles = array();
1843 
1844  # if page content file supplied
1845  if ($PageContentFile)
1846  {
1847  # if file containing list of required files is available
1848  $Path = dirname($PageContentFile);
1849  $RequireListFile = $Path."/REQUIRES";
1850  if (file_exists($RequireListFile))
1851  {
1852  # read in list of required files
1853  $RequestedFiles = file($RequireListFile);
1854 
1855  # for each line in required file list
1856  foreach ($RequestedFiles as $Line)
1857  {
1858  # if line is not a comment
1859  $Line = trim($Line);
1860  if (!preg_match("/^#/", $Line))
1861  {
1862  # if file has not already been loaded
1863  if (!in_array($Line, $this->FoundUIFiles))
1864  {
1865  # add to list of required files
1866  $RequiredFiles[] = $Line;
1867  }
1868  }
1869  }
1870  }
1871  }
1872 
1873  # add in additional required files if any
1874  if (count($this->AdditionalRequiredUIFiles))
1875  {
1876  # make sure there are no duplicates
1877  $AdditionalRequiredUIFiles = array_unique(
1878  $this->AdditionalRequiredUIFiles);
1879 
1880  $RequiredFiles = array_merge(
1881  $RequiredFiles, $AdditionalRequiredUIFiles);
1882  }
1883 
1884  # return list of required files to caller
1885  return $RequiredFiles;
1886  }
1887 
1891  private function SetUpObjectAutoloading()
1892  {
1893  function __autoload($ClassName)
1894  {
1895  ApplicationFramework::AutoloadObjects($ClassName);
1896  }
1897  }
1898 
1904  static function AutoloadObjects($ClassName)
1905  {
1906  static $FileLists;
1907  foreach (self::$ObjectDirectories as $Location => $Info)
1908  {
1909  if (is_dir($Location))
1910  {
1911  $NewClassName = ($Info["ClassPattern"] && $Info["ClassReplacement"])
1912  ? preg_replace($Info["ClassPattern"],
1913  $Info["ClassReplacement"], $ClassName)
1914  : $ClassName;
1915  if (!isset($FileLists[$Location]))
1916  {
1917  $FileLists[$Location] = self::ReadDirectoryTree(
1918  $Location, '/^.+\.php$/i');
1919  }
1920  $FileNames = $FileLists[$Location];
1921  $TargetName = strtolower($Info["Prefix"].$NewClassName.".php");
1922  foreach ($FileNames as $FileName)
1923  {
1924  if (strtolower($FileName) == $TargetName)
1925  {
1926  require_once($Location.$FileName);
1927  break 2;
1928  }
1929  }
1930  }
1931  }
1932  }
1933 
1941  private static function ReadDirectoryTree($Directory, $Pattern)
1942  {
1943  $CurrentDir = getcwd();
1944  chdir($Directory);
1945  $DirIter = new RecursiveDirectoryIterator(".");
1946  $IterIter = new RecursiveIteratorIterator($DirIter);
1947  $RegexResults = new RegexIterator($IterIter, $Pattern,
1948  RecursiveRegexIterator::GET_MATCH);
1949  $FileList = array();
1950  foreach ($RegexResults as $Result)
1951  {
1952  $FileList[] = substr($Result[0], 2);
1953  }
1954  chdir($CurrentDir);
1955  return $FileList;
1956  }
1963  private function LoadUIFunctions()
1964  {
1965  $Dirs = array(
1966  "local/interface/%ACTIVEUI%/include",
1967  "interface/%ACTIVEUI%/include",
1968  "local/interface/default/include",
1969  "interface/default/include",
1970  );
1971  foreach ($Dirs as $Dir)
1972  {
1973  $Dir = str_replace("%ACTIVEUI%", $this->ActiveUI, $Dir);
1974  if (is_dir($Dir))
1975  {
1976  $FileNames = scandir($Dir);
1977  foreach ($FileNames as $FileName)
1978  {
1979  if (preg_match("/^F-([A-Za-z_]+)\.php/", $FileName, $Matches)
1980  || preg_match("/^F-([A-Za-z_]+)\.html/", $FileName, $Matches))
1981  {
1982  if (!function_exists($Matches[1]))
1983  {
1984  include_once($Dir."/".$FileName);
1985  }
1986  }
1987  }
1988  }
1989  }
1990  }
1991 
1997  private function ProcessPeriodicEvent($EventName, $Callback)
1998  {
1999  # retrieve last execution time for event if available
2000  $Signature = self::GetCallbackSignature($Callback);
2001  $LastRun = $this->DB->Query("SELECT LastRunAt FROM PeriodicEvents"
2002  ." WHERE Signature = '".addslashes($Signature)."'", "LastRunAt");
2003 
2004  # determine whether enough time has passed for event to execute
2005  $EventPeriods = array(
2006  "EVENT_HOURLY" => 60*60,
2007  "EVENT_DAILY" => 60*60*24,
2008  "EVENT_WEEKLY" => 60*60*24*7,
2009  "EVENT_MONTHLY" => 60*60*24*30,
2010  "EVENT_PERIODIC" => 0,
2011  );
2012  $ShouldExecute = (($LastRun === NULL)
2013  || (time() > (strtotime($LastRun) + $EventPeriods[$EventName])))
2014  ? TRUE : FALSE;
2015 
2016  # if event should run
2017  if ($ShouldExecute)
2018  {
2019  # add event to task queue
2020  $WrapperCallback = array("ApplicationFramework", "PeriodicEventWrapper");
2021  $WrapperParameters = array(
2022  $EventName, $Callback, array("LastRunAt" => $LastRun));
2023  $this->QueueUniqueTask($WrapperCallback, $WrapperParameters);
2024  }
2025  }
2026 
2034  private static function PeriodicEventWrapper($EventName, $Callback, $Parameters)
2035  {
2036  static $DB;
2037  if (!isset($DB)) { $DB = new Database(); }
2038 
2039  # run event
2040  $ReturnVal = call_user_func_array($Callback, $Parameters);
2041 
2042  # if event is already in database
2043  $Signature = self::GetCallbackSignature($Callback);
2044  if ($DB->Query("SELECT COUNT(*) AS EventCount FROM PeriodicEvents"
2045  ." WHERE Signature = '".addslashes($Signature)."'", "EventCount"))
2046  {
2047  # update last run time for event
2048  $DB->Query("UPDATE PeriodicEvents SET LastRunAt = "
2049  .(($EventName == "EVENT_PERIODIC")
2050  ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
2051  : "NOW()")
2052  ." WHERE Signature = '".addslashes($Signature)."'");
2053  }
2054  else
2055  {
2056  # add last run time for event to database
2057  $DB->Query("INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES "
2058  ."('".addslashes($Signature)."', "
2059  .(($EventName == "EVENT_PERIODIC")
2060  ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
2061  : "NOW()").")");
2062  }
2063  }
2064 
2070  private static function GetCallbackSignature($Callback)
2071  {
2072  return !is_array($Callback) ? $Callback
2073  : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
2074  ."::".$Callback[1];
2075  }
2076 
2081  private function PrepForTSR()
2082  {
2083  # if HTML has been output and it's time to launch another task
2084  # (only TSR if HTML has been output because otherwise browsers
2085  # may misbehave after connection is closed)
2086  if (($this->JumpToPage || !$this->SuppressHTML)
2087  && (time() > (strtotime($this->Settings["LastTaskRunAt"])
2088  + (ini_get("max_execution_time")
2089  / $this->Settings["MaxTasksRunning"]) + 5))
2090  && $this->GetTaskQueueSize()
2091  && $this->Settings["TaskExecutionEnabled"])
2092  {
2093  # begin buffering output for TSR
2094  ob_start();
2095 
2096  # let caller know it is time to launch another task
2097  return TRUE;
2098  }
2099  else
2100  {
2101  # let caller know it is not time to launch another task
2102  return FALSE;
2103  }
2104  }
2105 
2110  private function LaunchTSR()
2111  {
2112  # set headers to close out connection to browser
2113  if (!$this->NoTSR)
2114  {
2115  ignore_user_abort(TRUE);
2116  header("Connection: close");
2117  header("Content-Length: ".ob_get_length());
2118  }
2119 
2120  # output buffered content
2121  ob_end_flush();
2122  flush();
2123 
2124  # write out any outstanding data and end HTTP session
2125  session_write_close();
2126 
2127  # set flag indicating that we are now running in background
2128  $this->RunningInBackground = TRUE;
2129 
2130  # if there is still a task in the queue
2131  if ($this->GetTaskQueueSize())
2132  {
2133  # turn on output buffering to (hopefully) record any crash output
2134  ob_start();
2135 
2136  # lock tables and grab last task run time to double check
2137  $this->DB->Query("LOCK TABLES ApplicationFrameworkSettings WRITE");
2138  $this->LoadSettings();
2139 
2140  # if still time to launch another task
2141  if (time() > (strtotime($this->Settings["LastTaskRunAt"])
2142  + (ini_get("max_execution_time")
2143  / $this->Settings["MaxTasksRunning"]) + 5))
2144  {
2145  # update the "last run" time and release tables
2146  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
2147  ." SET LastTaskRunAt = '".date("Y-m-d H:i:s")."'");
2148  $this->DB->Query("UNLOCK TABLES");
2149 
2150  # run tasks while there is a task in the queue and enough time left
2151  do
2152  {
2153  # run the next task
2154  $this->RunNextTask();
2155  }
2156  while ($this->GetTaskQueueSize()
2157  && ($this->GetSecondsBeforeTimeout() > 65));
2158  }
2159  else
2160  {
2161  # release tables
2162  $this->DB->Query("UNLOCK TABLES");
2163  }
2164  }
2165  }
2166 
2174  private function GetTaskList($DBQuery, $Count, $Offset)
2175  {
2176  $this->DB->Query($DBQuery." LIMIT ".intval($Offset).",".intval($Count));
2177  $Tasks = array();
2178  while ($Row = $this->DB->FetchRow())
2179  {
2180  $Tasks[$Row["TaskId"]] = $Row;
2181  if ($Row["Callback"] ==
2182  serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
2183  {
2184  $WrappedCallback = unserialize($Row["Parameters"]);
2185  $Tasks[$Row["TaskId"]]["Callback"] = $WrappedCallback[1];
2186  $Tasks[$Row["TaskId"]]["Parameters"] = NULL;
2187  }
2188  else
2189  {
2190  $Tasks[$Row["TaskId"]]["Callback"] = unserialize($Row["Callback"]);
2191  $Tasks[$Row["TaskId"]]["Parameters"] = unserialize($Row["Parameters"]);
2192  }
2193  }
2194  return $Tasks;
2195  }
2196 
2200  private function RunNextTask()
2201  {
2202  # look for task at head of queue
2203  $this->DB->Query("SELECT * FROM TaskQueue ORDER BY Priority, TaskId LIMIT 1");
2204  $Task = $this->DB->FetchRow();
2205 
2206  # if there was a task available
2207  if ($Task)
2208  {
2209  # move task from queue to running tasks list
2210  $this->DB->Query("INSERT INTO RunningTasks "
2211  ."(TaskId,Callback,Parameters,Priority,Description) "
2212  ."SELECT * FROM TaskQueue WHERE TaskId = "
2213  .intval($Task["TaskId"]));
2214  $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = "
2215  .intval($Task["TaskId"]));
2216 
2217  # unpack stored task info
2218  $Callback = unserialize($Task["Callback"]);
2219  $Parameters = unserialize($Task["Parameters"]);
2220 
2221  # attempt to load task callback if not already available
2222  $this->LoadFunction($Callback);
2223 
2224  # run task
2225  $this->RunningTask = $Task;
2226  if ($Parameters)
2227  {
2228  call_user_func_array($Callback, $Parameters);
2229  }
2230  else
2231  {
2232  call_user_func($Callback);
2233  }
2234  unset($this->RunningTask);
2235 
2236  # remove task from running tasks list
2237  $this->DB->Query("DELETE FROM RunningTasks"
2238  ." WHERE TaskId = ".intval($Task["TaskId"]));
2239 
2240  # prune running tasks list if necessary
2241  $RunningTasksCount = $this->DB->Query(
2242  "SELECT COUNT(*) AS TaskCount FROM RunningTasks", "TaskCount");
2243  if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
2244  {
2245  $this->DB->Query("DELETE FROM RunningTasks ORDER BY StartedAt"
2246  ." LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
2247  }
2248  }
2249  }
2250 
2256  function OnCrash()
2257  {
2258  if (isset($this->RunningTask))
2259  {
2260  if (function_exists("error_get_last"))
2261  {
2262  $CrashInfo["LastError"] = error_get_last();
2263  }
2264  if (ob_get_length() !== FALSE)
2265  {
2266  $CrashInfo["OutputBuffer"] = ob_get_contents();
2267  }
2268  if (isset($CrashInfo))
2269  {
2270  $DB = new Database();
2271  $DB->Query("UPDATE RunningTasks SET CrashInfo = '"
2272  .addslashes(serialize($CrashInfo))
2273  ."' WHERE TaskId = ".intval($this->RunningTask["TaskId"]));
2274  }
2275  }
2276 
2277  print("\n");
2278  return;
2279 
2280  if (ob_get_length() !== FALSE)
2281  {
2282  ?>
2283  <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>
2284  <div style="font-size: 200%;">CRASH OUTPUT</div><?PHP
2285  ob_end_flush();
2286  ?></td></tr></table><?PHP
2287  }
2288  }
2289 
2306  private function AddToDirList($DirList, $Dir, $SearchLast, $SkipSlashCheck)
2307  {
2308  # convert incoming directory to array of directories (if needed)
2309  $Dirs = is_array($Dir) ? $Dir : array($Dir);
2310 
2311  # reverse array so directories are searched in specified order
2312  $Dirs = array_reverse($Dirs);
2313 
2314  # for each directory
2315  foreach ($Dirs as $Location)
2316  {
2317  # make sure directory includes trailing slash
2318  if (!$SkipSlashCheck)
2319  {
2320  $Location = $Location
2321  .((substr($Location, -1) != "/") ? "/" : "");
2322  }
2323 
2324  # remove directory from list if already present
2325  if (in_array($Location, $DirList))
2326  {
2327  $DirList = array_diff(
2328  $DirList, array($Location));
2329  }
2330 
2331  # add directory to list of directories
2332  if ($SearchLast)
2333  {
2334  array_push($DirList, $Location);
2335  }
2336  else
2337  {
2338  array_unshift($DirList, $Location);
2339  }
2340  }
2341 
2342  # return updated directory list to caller
2343  return $DirList;
2344  }
2345 
2347  private $InterfaceDirList = array(
2348  "local/interface/%ACTIVEUI%/",
2349  "interface/%ACTIVEUI%/",
2350  "local/interface/default/",
2351  "interface/default/",
2352  );
2357  private $IncludeDirList = array(
2358  "local/interface/%ACTIVEUI%/include/",
2359  "interface/%ACTIVEUI%/include/",
2360  "local/interface/default/include/",
2361  "interface/default/include/",
2362  );
2364  private $ImageDirList = array(
2365  "local/interface/%ACTIVEUI%/images/",
2366  "interface/%ACTIVEUI%/images/",
2367  "local/interface/default/images/",
2368  "interface/default/images/",
2369  );
2371  private $FunctionDirList = array(
2372  "local/interface/%ACTIVEUI%/include/",
2373  "interface/%ACTIVEUI%/include/",
2374  "local/interface/default/include/",
2375  "interface/default/include/",
2376  "local/include/",
2377  "include/",
2378  );
2379 
2380  const NOVALUE = ".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
2381 };
2382 
2383 
2384 ?>