CWIS Developer Documentation
ApplicationFramework.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: ApplicationFramework.php
4 #
5 # Part of the ScoutLib application support library
6 # Copyright 2009-2013 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu
8 #
9 
15 
16  # ---- PUBLIC INTERFACE --------------------------------------------------
17  /*@(*/
19 
24  function __construct()
25  {
26  # save execution start time
27  $this->ExecutionStartTime = microtime(TRUE);
28 
29  # begin/restore PHP session
30  $SessionDomain = isset($_SERVER["SERVER_NAME"]) ? $_SERVER["SERVER_NAME"]
31  : isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"]
32  : php_uname("n");
33  if (is_writable(session_save_path()))
34  {
35  $SessionStorage = session_save_path()
36  ."/".self::$AppName."_".md5($SessionDomain.dirname(__FILE__));
37  if (!is_dir($SessionStorage)) { mkdir($SessionStorage, 0700 ); }
38  if (is_writable($SessionStorage)) { session_save_path($SessionStorage); }
39  }
40  ini_set("session.gc_maxlifetime", self::$SessionLifetime);
41  session_set_cookie_params(
42  self::$SessionLifetime, "/", $SessionDomain);
43  session_start();
44 
45  # set up object file autoloader
46  $this->SetUpObjectAutoloading();
47 
48  # set up function to output any buffered text in case of crash
49  register_shutdown_function(array($this, "OnCrash"));
50 
51  # set up our internal environment
52  $this->DB = new Database();
53 
54  # set up our exception handler
55  set_exception_handler(array($this, "GlobalExceptionHandler"));
56 
57  # perform any work needed to undo PHP magic quotes
58  $this->UndoMagicQuotes();
59 
60  # load our settings from database
61  $this->LoadSettings();
62 
63  # set PHP maximum execution time
64  $this->MaxExecutionTime($this->Settings["MaxExecTime"]);
65 
66  # register events we handle internally
67  $this->RegisterEvent($this->PeriodicEvents);
68  $this->RegisterEvent($this->UIEvents);
69  }
76  function __destruct()
77  {
78  # if template location cache is flagged to be saved
79  if ($this->SaveTemplateLocationCache)
80  {
81  # write template location cache out and update cache expiration
82  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
83  ." SET TemplateLocationCache = '"
84  .addslashes(serialize(
85  $this->Settings["TemplateLocationCache"]))."',"
86  ." TemplateLocationCacheExpiration = "
87  ." NOW() + INTERVAL "
88  .$this->Settings["TemplateLocationCacheInterval"]
89  ." MINUTE");
90  }
91 
92  # if object location cache is flagged to be saved
93  if (self::$SaveObjectLocationCache)
94  {
95  # write object location cache out and update cache expiration
96  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
97  ." SET ObjectLocationCache = '"
98  .addslashes(serialize(
99  self::$ObjectLocationCache))."',"
100  ." ObjectLocationCacheExpiration = "
101  ." NOW() + INTERVAL "
102  .self::$ObjectLocationCacheInterval
103  ." MINUTE");
104  }
105  }
112  function GlobalExceptionHandler($Exception)
113  {
114  # display exception info
115  $Location = $Exception->getFile()."[".$Exception->getLine()."]";
116  ?><table width="100%" cellpadding="5"
117  style="border: 2px solid #666666; background: #CCCCCC;
118  font-family: Courier New, Courier, monospace;
119  margin-top: 10px;"><tr><td>
120  <div style="color: #666666;">
121  <span style="font-size: 150%;">
122  <b>Uncaught Exception</b></span><br />
123  <b>Message:</b> <i><?PHP print $Exception->getMessage(); ?></i><br />
124  <b>Location:</b> <i><?PHP print $Location; ?></i><br />
125  <b>Trace:</b>
126  <blockquote><pre><?PHP print $Exception->getTraceAsString();
127  ?></pre></blockquote>
128  </div>
129  </td></tr></table><?PHP
130 
131  # log exception if possible
132  $LogMsg = "Uncaught exception (".$Exception->getMessage().").";
133  $this->LogError(self::LOGLVL_ERROR, $LogMsg);
134  }
148  static function AddObjectDirectory(
149  $Dir, $Prefix = "", $ClassPattern = NULL, $ClassReplacement = NULL)
150  {
151  # make sure directory has trailing slash
152  $Dir = $Dir.((substr($Dir, -1) != "/") ? "/" : "");
153 
154  # add directory to directory list
155  self::$ObjectDirectories = array_merge(
156  array($Dir => array(
157  "Prefix" => $Prefix,
158  "ClassPattern" => $ClassPattern,
159  "ClassReplacement" => $ClassReplacement,
160  )),
161  self::$ObjectDirectories);
162  }
163 
183  function AddImageDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
184  {
185  # add directories to existing image directory list
186  $this->ImageDirList = $this->AddToDirList(
187  $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
188  }
189 
210  function AddIncludeDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
211  {
212  # add directories to existing image directory list
213  $this->IncludeDirList = $this->AddToDirList(
214  $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
215  }
216 
236  function AddInterfaceDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
237  {
238  # add directories to existing image directory list
239  $this->InterfaceDirList = $this->AddToDirList(
240  $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
241  }
242 
262  function AddFunctionDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
263  {
264  # add directories to existing image directory list
265  $this->FunctionDirList = $this->AddToDirList(
266  $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
267  }
268 
274  function SetBrowserDetectionFunc($DetectionFunc)
275  {
276  $this->BrowserDetectFunc = $DetectionFunc;
277  }
278 
285  function AddUnbufferedCallback($Callback, $Parameters=array())
286  {
287  if (is_callable($Callback))
288  {
289  $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
290  }
291  }
292 
300  {
301  return $this->UpdateSetting("TemplateLocationCacheInterval", $NewInterval);
302  }
303 
311  {
312  return $this->UpdateSetting("ObjectLocationCacheInterval", $NewInterval);
313  }
314 
320  static function SessionLifetime($NewValue = NULL)
321  {
322  if ($NewValue !== NULL)
323  {
324  self::$SessionLifetime = $NewValue;
325  }
326  return self::$SessionLifetime;
327  }
328 
355  function AddCleanUrl($Pattern, $Page, $GetVars = NULL, $Template = NULL)
356  {
357  # save clean URL mapping parameters
358  $this->CleanUrlMappings[] = array(
359  "Pattern" => $Pattern,
360  "Page" => $Page,
361  "GetVars" => $GetVars,
362  );
363 
364  # if replacement template specified
365  if ($Template !== NULL)
366  {
367  # if GET parameters specified
368  if (count($GetVars))
369  {
370  # retrieve all possible permutations of GET parameters
371  $GetPerms = $this->ArrayPermutations(array_keys($GetVars));
372 
373  # for each permutation of GET parameters
374  foreach ($GetPerms as $VarPermutation)
375  {
376  # construct search pattern for permutation
377  $SearchPattern = "/href=([\"'])index\\.php\\?P=".$Page;
378  $GetVarSegment = "";
379  foreach ($VarPermutation as $GetVar)
380  {
381  if (preg_match("%\\\$[0-9]+%", $GetVars[$GetVar]))
382  {
383  $GetVarSegment .= "&amp;".$GetVar."=((?:(?!\\1)[^&])+)";
384  }
385  else
386  {
387  $GetVarSegment .= "&amp;".$GetVar."=".$GetVars[$GetVar];
388  }
389  }
390  $SearchPattern .= $GetVarSegment."\\1/i";
391 
392  # if template is actually a callback
393  if (is_callable($Template))
394  {
395  # add pattern to HTML output mod callbacks list
396  $this->OutputModificationCallbacks[] = array(
397  "Pattern" => $Pattern,
398  "Page" => $Page,
399  "SearchPattern" => $SearchPattern,
400  "Callback" => $Template,
401  );
402  }
403  else
404  {
405  # construct replacement string for permutation
406  $Replacement = $Template;
407  $Index = 2;
408  foreach ($VarPermutation as $GetVar)
409  {
410  $Replacement = str_replace(
411  "\$".$GetVar, "\$".$Index, $Replacement);
412  $Index++;
413  }
414  $Replacement = "href=\"".$Replacement."\"";
415 
416  # add pattern to HTML output modifications list
417  $this->OutputModificationPatterns[] = $SearchPattern;
418  $this->OutputModificationReplacements[] = $Replacement;
419  }
420  }
421  }
422  else
423  {
424  # construct search pattern
425  $SearchPattern = "/href=\"index\\.php\\?P=".$Page."\"/i";
426 
427  # if template is actually a callback
428  if (is_callable($Template))
429  {
430  # add pattern to HTML output mod callbacks list
431  $this->OutputModificationCallbacks[] = array(
432  "Pattern" => $Pattern,
433  "Page" => $Page,
434  "SearchPattern" => $SearchPattern,
435  "Callback" => $Template,
436  );
437  }
438  else
439  {
440  # add simple pattern to HTML output modifications list
441  $this->OutputModificationPatterns[] = $SearchPattern;
442  $this->OutputModificationReplacements[] = "href=\"".$Template."\"";
443  }
444  }
445  }
446  }
447 
453  function CleanUrlIsMapped($Path)
454  {
455  foreach ($this->CleanUrlMappings as $Info)
456  {
457  if (preg_match($Info["Pattern"], $Path))
458  {
459  return TRUE;
460  }
461  }
462  return FALSE;
463  }
464 
471  function GetCleanUrlForPath($Path)
472  {
473  # the search patterns and callbacks require a specific format
474  $Format = "href=\"".str_replace("&", "&amp;", $Path)."\"";
475  $Search = $Format;
476 
477  # perform any regular expression replacements on the search string
478  $Search = preg_replace(
479  $this->OutputModificationPatterns,
480  $this->OutputModificationReplacements,
481  $Search);
482 
483  # only run the callbacks if a replacement hasn't already been performed
484  if ($Search == $Format)
485  {
486  # perform any callback replacements on the search string
487  foreach ($this->OutputModificationCallbacks as $Info)
488  {
489  # make the information available to the callback
490  $this->OutputModificationCallbackInfo = $Info;
491 
492  # execute the callback
493  $Search = preg_replace_callback(
494  $Info["SearchPattern"],
495  array($this, "OutputModificationCallbackShell"),
496  $Search);
497  }
498  }
499 
500  # return the path untouched if no replacements were performed
501  if ($Search == $Format)
502  {
503  return $Path;
504  }
505 
506  # remove the bits added to the search string to get it recognized by
507  # the replacement expressions and callbacks
508  $Result = substr($Search, 6, -1);
509 
510  return $Result;
511  }
512 
519  public function GetUncleanUrlForPath($Path)
520  {
521  # for each clean URL mapping
522  foreach ($this->CleanUrlMappings as $Info)
523  {
524  # if current path matches the clean URL pattern
525  if (preg_match($Info["Pattern"], $Path, $Matches))
526  {
527  # the GET parameters for the URL, starting with the page name
528  $GetVars = array("P" => $Info["Page"]);
529 
530  # if additional $_GET variables specified for clean URL
531  if ($Info["GetVars"] !== NULL)
532  {
533  # for each $_GET variable specified for clean URL
534  foreach ($Info["GetVars"] as $VarName => $VarTemplate)
535  {
536  # start with template for variable value
537  $Value = $VarTemplate;
538 
539  # for each subpattern matched in current URL
540  foreach ($Matches as $Index => $Match)
541  {
542  # if not first (whole) match
543  if ($Index > 0)
544  {
545  # make any substitutions in template
546  $Value = str_replace("$".$Index, $Match, $Value);
547  }
548  }
549 
550  # add the GET variable
551  $GetVars[$VarName] = $Value;
552  }
553  }
554 
555  # return the unclean URL
556  return "index.php?" . http_build_query($GetVars);
557  }
558  }
559 
560  # return the path unchanged
561  return $Path;
562  }
563 
569  function GetCleanUrl()
570  {
571  return $this->GetCleanUrlForPath($this->GetUncleanUrl());
572  }
573 
578  function GetUncleanUrl()
579  {
580  $GetVars = array("P" => $this->GetPageName()) + $_GET;
581  return "index.php?" . http_build_query($GetVars);
582  }
583 
588  function LoadPage($PageName)
589  {
590  # perform any clean URL rewriting
591  $PageName = $this->RewriteCleanUrls($PageName);
592 
593  # sanitize incoming page name and save local copy
594  $PageName = preg_replace("/[^a-zA-Z0-9_.-]/", "", $PageName);
595  $this->PageName = $PageName;
596 
597  # buffer any output from includes or PHP file
598  ob_start();
599 
600  # include any files needed to set up execution environment
601  foreach ($this->EnvIncludes as $IncludeFile)
602  {
603  include($IncludeFile);
604  }
605 
606  # signal page load
607  $this->SignalEvent("EVENT_PAGE_LOAD", array("PageName" => $PageName));
608 
609  # signal PHP file load
610  $SignalResult = $this->SignalEvent("EVENT_PHP_FILE_LOAD", array(
611  "PageName" => $PageName));
612 
613  # if signal handler returned new page name value
614  $NewPageName = $PageName;
615  if (($SignalResult["PageName"] != $PageName)
616  && strlen($SignalResult["PageName"]))
617  {
618  # if new page name value is page file
619  if (file_exists($SignalResult["PageName"]))
620  {
621  # use new value for PHP file name
622  $PageFile = $SignalResult["PageName"];
623  }
624  else
625  {
626  # use new value for page name
627  $NewPageName = $SignalResult["PageName"];
628  }
629 
630  # update local copy of page name
631  $this->PageName = $NewPageName;
632  }
633 
634  # if we do not already have a PHP file
635  if (!isset($PageFile))
636  {
637  # look for PHP file for page
638  $OurPageFile = "pages/".$NewPageName.".php";
639  $LocalPageFile = "local/pages/".$NewPageName.".php";
640  $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
641  : (file_exists($OurPageFile) ? $OurPageFile
642  : "pages/".$this->DefaultPage.".php");
643  }
644 
645  # load PHP file
646  include($PageFile);
647 
648  # save buffered output to be displayed later after HTML file loads
649  $PageOutput = ob_get_contents();
650  ob_end_clean();
651 
652  # signal PHP file load is complete
653  ob_start();
654  $Context["Variables"] = get_defined_vars();
655  $this->SignalEvent("EVENT_PHP_FILE_LOAD_COMPLETE",
656  array("PageName" => $PageName, "Context" => $Context));
657  $PageCompleteOutput = ob_get_contents();
658  ob_end_clean();
659 
660  # set up for possible TSR (Terminate and Stay Resident :))
661  $ShouldTSR = $this->PrepForTSR();
662 
663  # if PHP file indicated we should autorefresh to somewhere else
664  if ($this->JumpToPage)
665  {
666  if (!strlen(trim($PageOutput)))
667  {
668  ?><html>
669  <head>
670  <meta http-equiv="refresh" content="0; URL=<?PHP
671  print($this->JumpToPage); ?>">
672  </head>
673  <body bgcolor="white">
674  </body>
675  </html><?PHP
676  }
677  }
678  # else if HTML loading is not suppressed
679  elseif (!$this->SuppressHTML)
680  {
681  # set content-type to get rid of diacritic errors
682  header("Content-Type: text/html; charset="
683  .$this->HtmlCharset, TRUE);
684 
685  # load common HTML file (defines common functions) if available
686  $CommonHtmlFile = $this->FindFile($this->IncludeDirList,
687  "Common", array("tpl", "html"));
688  if ($CommonHtmlFile) { include($CommonHtmlFile); }
689 
690  # load UI functions
691  $this->LoadUIFunctions();
692 
693  # begin buffering content
694  ob_start();
695 
696  # signal HTML file load
697  $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD", array(
698  "PageName" => $PageName));
699 
700  # if signal handler returned new page name value
701  $NewPageName = $PageName;
702  $PageContentFile = NULL;
703  if (($SignalResult["PageName"] != $PageName)
704  && strlen($SignalResult["PageName"]))
705  {
706  # if new page name value is HTML file
707  if (file_exists($SignalResult["PageName"]))
708  {
709  # use new value for HTML file name
710  $PageContentFile = $SignalResult["PageName"];
711  }
712  else
713  {
714  # use new value for page name
715  $NewPageName = $SignalResult["PageName"];
716  }
717  }
718 
719  # load page content HTML file if available
720  if ($PageContentFile === NULL)
721  {
722  $PageContentFile = $this->FindFile(
723  $this->InterfaceDirList, $NewPageName,
724  array("tpl", "html"));
725  }
726  if ($PageContentFile)
727  {
728  include($PageContentFile);
729  }
730  else
731  {
732  print "<h2>ERROR: No HTML/TPL template found"
733  ." for this page.</h2>";
734  }
735 
736  # signal HTML file load complete
737  $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD_COMPLETE");
738 
739  # stop buffering and save output
740  $PageContentOutput = ob_get_contents();
741  ob_end_clean();
742 
743  # load page start HTML file if available
744  ob_start();
745  $PageStartFile = $this->FindFile($this->IncludeDirList, "Start",
746  array("tpl", "html"), array("StdPage", "StandardPage"));
747  if ($PageStartFile) { include($PageStartFile); }
748  $PageStartOutput = ob_get_contents();
749  ob_end_clean();
750 
751  # load page end HTML file if available
752  ob_start();
753  $PageEndFile = $this->FindFile($this->IncludeDirList, "End",
754  array("tpl", "html"), array("StdPage", "StandardPage"));
755  if ($PageEndFile) { include($PageEndFile); }
756  $PageEndOutput = ob_get_contents();
757  ob_end_clean();
758 
759  # get list of any required files not loaded
760  $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
761 
762  # if a browser detection function has been made available
763  if (is_callable($this->BrowserDetectFunc))
764  {
765  # call function to get browser list
766  $Browsers = call_user_func($this->BrowserDetectFunc);
767 
768  # for each required file
769  $NewRequiredFiles = array();
770  foreach ($RequiredFiles as $File)
771  {
772  # if file name includes browser keyword
773  if (preg_match("/%BROWSER%/", $File))
774  {
775  # for each browser
776  foreach ($Browsers as $Browser)
777  {
778  # substitute in browser name and add to new file list
779  $NewRequiredFiles[] = preg_replace(
780  "/%BROWSER%/", $Browser, $File);
781  }
782  }
783  else
784  {
785  # add to new file list
786  $NewRequiredFiles[] = $File;
787  }
788  }
789  $RequiredFiles = $NewRequiredFiles;
790  }
791 
792  # for each required file
793  foreach ($RequiredFiles as $File)
794  {
795  # locate specific file to use
796  $FilePath = $this->GUIFile($File);
797 
798  # if file was found
799  if ($FilePath)
800  {
801  # determine file type
802  $NamePieces = explode(".", $File);
803  $FileSuffix = strtolower(array_pop($NamePieces));
804 
805  # add file to HTML output based on file type
806  $FilePath = htmlspecialchars($FilePath);
807  switch ($FileSuffix)
808  {
809  case "js":
810  $Tag = '<script type="text/javascript" src="'
811  .$FilePath.'"></script>';
812  $PageEndOutput = preg_replace(
813  "#</body>#i", $Tag."\n</body>", $PageEndOutput, 1);
814  break;
815 
816  case "css":
817  $Tag = '<link rel="stylesheet" type="text/css"'
818  .' media="all" href="'.$FilePath.'">';
819  $PageStartOutput = preg_replace(
820  "#</head>#i", $Tag."\n</head>", $PageStartOutput, 1);
821  break;
822  }
823  }
824  }
825 
826  # assemble full page
827  $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
828 
829  # perform any regular expression replacements in output
830  $FullPageOutput = preg_replace($this->OutputModificationPatterns,
831  $this->OutputModificationReplacements, $FullPageOutput);
832 
833  # perform any callback replacements in output
834  foreach ($this->OutputModificationCallbacks as $Info)
835  {
836  $this->OutputModificationCallbackInfo = $Info;
837  $FullPageOutput = preg_replace_callback($Info["SearchPattern"],
838  array($this, "OutputModificationCallbackShell"),
839  $FullPageOutput);
840  }
841 
842  # if relative paths may not work because we were invoked via clean URL
843  if ($this->CleanUrlRewritePerformed || self::WasUrlRewritten())
844  {
845  # if using the <base> tag is okay
846  $BaseUrl = $this->BaseUrl();
847  if ($this->UseBaseTag)
848  {
849  # add <base> tag to header
850  $PageStartOutput = preg_replace("%<head>%",
851  "<head><base href=\"".$BaseUrl."\" />",
852  $PageStartOutput);
853 
854  # re-assemble full page with new header
855  $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
856 
857  # the absolute URL to the current page
858  $FullUrl = $BaseUrl . $this->GetPageLocation();
859 
860  # make HREF attribute values with just a fragment ID
861  # absolute since they don't work with the <base> tag because
862  # they are relative to the current page/URL, not the site
863  # root
864  $FullPageOutput = preg_replace(
865  array("%href=\"(#[^:\" ]+)\"%i", "%href='(#[^:' ]+)'%i"),
866  array("href=\"".$FullUrl."$1\"", "href='".$FullUrl."$1'"),
867  $FullPageOutput);
868  }
869  else
870  {
871  # try to fix any relative paths throughout code
872  $FullPageOutput = preg_replace(array(
873  "%src=\"([^?*:;{}\\\\\" ]+)\.(js|css|gif|png|jpg)\"%i",
874  "%src='([^?*:;{}\\\\' ]+)\.(js|css|gif|png|jpg)'%i",
875  # don't rewrite HREF attributes that are just
876  # fragment IDs because they are relative to the
877  # current page/URL, not the site root
878  "%href=\"([^#][^:\" ]*)\"%i",
879  "%href='([^#][^:' ]*)'%i",
880  "%action=\"([^#][^:\" ]*)\"%i",
881  "%action='([^#][^:' ]*)'%i",
882  "%@import\s+url\(\"([^:\" ]+)\"\s*\)%i",
883  "%@import\s+url\('([^:\" ]+)'\s*\)%i",
884  "%@import\s+\"([^:\" ]+)\"\s*%i",
885  "%@import\s+'([^:\" ]+)'\s*%i",
886  ),
887  array(
888  "src=\"".$BaseUrl."$1.$2\"",
889  "src=\"".$BaseUrl."$1.$2\"",
890  "href=\"".$BaseUrl."$1\"",
891  "href=\"".$BaseUrl."$1\"",
892  "action=\"".$BaseUrl."$1\"",
893  "action=\"".$BaseUrl."$1\"",
894  "@import url(\"".$BaseUrl."$1\")",
895  "@import url('".$BaseUrl."$1')",
896  "@import \"".$BaseUrl."$1\"",
897  "@import '".$BaseUrl."$1'",
898  ),
899  $FullPageOutput);
900  }
901  }
902 
903  # provide the opportunity to modify full page output
904  $SignalResult = $this->SignalEvent("EVENT_PAGE_OUTPUT_FILTER", array(
905  "PageOutput" => $FullPageOutput));
906  if (isset($SignalResult["PageOutput"])
907  && strlen($SignalResult["PageOutput"]))
908  {
909  $FullPageOutput = $SignalResult["PageOutput"];
910  }
911 
912  # write out full page
913  print $FullPageOutput;
914  }
915 
916  # run any post-processing routines
917  foreach ($this->PostProcessingFuncs as $Func)
918  {
919  call_user_func_array($Func["FunctionName"], $Func["Arguments"]);
920  }
921 
922  # write out any output buffered from page code execution
923  if (strlen($PageOutput))
924  {
925  if (!$this->SuppressHTML)
926  {
927  ?><table width="100%" cellpadding="5"
928  style="border: 2px solid #666666; background: #CCCCCC;
929  font-family: Courier New, Courier, monospace;
930  margin-top: 10px;"><tr><td><?PHP
931  }
932  if ($this->JumpToPage)
933  {
934  ?><div style="color: #666666;"><span style="font-size: 150%;">
935  <b>Page Jump Aborted</b></span>
936  (because of error or other unexpected output)<br />
937  <b>Jump Target:</b>
938  <i><?PHP print($this->JumpToPage); ?></i></div><?PHP
939  }
940  print $PageOutput;
941  if (!$this->SuppressHTML)
942  {
943  ?></td></tr></table><?PHP
944  }
945  }
946 
947  # write out any output buffered from the page code execution complete signal
948  if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
949  {
950  print $PageCompleteOutput;
951  }
952 
953  # execute callbacks that should not have their output buffered
954  foreach ($this->UnbufferedCallbacks as $Callback)
955  {
956  call_user_func_array($Callback[0], $Callback[1]);
957  }
958 
959  # log slow page loads
960  if ($this->LogSlowPageLoads()
961  && ($this->GetElapsedExecutionTime() >= $this->SlowPageLoadThreshold()))
962  {
963  $SlowPageLoadMsg = "Slow page load ("
964  .intval($this->GetElapsedExecutionTime())."s) for "
965  .$this->FullUrl()." from "
966  .$_SERVER["REMOTE_ADDR"];
967  $this->LogMessage(self::LOGLVL_INFO, $SlowPageLoadMsg);
968  }
969 
970  # terminate and stay resident (TSR!) if indicated and HTML has been output
971  # (only TSR if HTML has been output because otherwise browsers will misbehave)
972  if ($ShouldTSR) { $this->LaunchTSR(); }
973  }
974 
980  function GetPageName()
981  {
982  return $this->PageName;
983  }
984 
990  function GetPageLocation()
991  {
992  # retrieve current URL
993  $Url = $this->GetScriptUrl();
994 
995  # remove the base path if present
996  $BasePath = $this->Settings["BasePath"];
997  if (stripos($Url, $BasePath) === 0)
998  {
999  $Url = substr($Url, strlen($BasePath));
1000  }
1001 
1002  return $Url;
1003  }
1004 
1009  function GetPageUrl()
1010  {
1011  return self::BaseUrl() . $this->GetPageLocation();
1012  }
1013 
1022  function SetJumpToPage($Page, $IsLiteral = FALSE)
1023  {
1024  if (!is_null($Page)
1025  && (!$IsLiteral)
1026  && (strpos($Page, "?") === FALSE)
1027  && ((strpos($Page, "=") !== FALSE)
1028  || ((stripos($Page, ".php") === FALSE)
1029  && (stripos($Page, ".htm") === FALSE)
1030  && (strpos($Page, "/") === FALSE)))
1031  && (stripos($Page, "http://") !== 0)
1032  && (stripos($Page, "https://") !== 0))
1033  {
1034  $this->JumpToPage = self::BaseUrl() . "index.php?P=".$Page;
1035  }
1036  else
1037  {
1038  $this->JumpToPage = $Page;
1039  }
1040  }
1041 
1046  function JumpToPageIsSet()
1047  {
1048  return ($this->JumpToPage === NULL) ? FALSE : TRUE;
1049  }
1050 
1060  function HtmlCharset($NewSetting = NULL)
1061  {
1062  if ($NewSetting !== NULL) { $this->HtmlCharset = $NewSetting; }
1063  return $this->HtmlCharset;
1064  }
1065 
1072  public function UseMinimizedJavascript($NewSetting = NULL)
1073  {
1074  if ($NewSetting !== NULL) { $this->UseMinimizedJavascript = $NewSetting; }
1075  return $this->UseMinimizedJavascript;
1076  }
1077 
1088  function UseBaseTag($NewValue = NULL)
1089  {
1090  if ($NewValue !== NULL) { $this->UseBaseTag = $NewValue ? TRUE : FALSE; }
1091  return $this->UseBaseTag;
1092  }
1093 
1100  function SuppressHTMLOutput($NewSetting = TRUE)
1101  {
1102  $this->SuppressHTML = $NewSetting;
1103  }
1104 
1111  function ActiveUserInterface($UIName = NULL)
1112  {
1113  if ($UIName !== NULL)
1114  {
1115  $this->ActiveUI = preg_replace("/^SPTUI--/", "", $UIName);
1116  }
1117  return $this->ActiveUI;
1118  }
1119 
1126  {
1127  # possible UI directories
1128  $InterfaceDirs = array(
1129  "interface",
1130  "local/interface");
1131 
1132  # start out with an empty list
1133  $Interfaces = array();
1134 
1135  # for each possible UI directory
1136  foreach ($InterfaceDirs as $InterfaceDir)
1137  {
1138  $Dir = dir($InterfaceDir);
1139 
1140  # for each file in current directory
1141  while (($DirEntry = $Dir->read()) !== FALSE)
1142  {
1143  $InterfacePath = $InterfaceDir."/".$DirEntry;
1144 
1145  # skip anything that doesn't have a name in the required format
1146  if (!preg_match('/^[a-zA-Z0-9]+$/', $DirEntry))
1147  {
1148  continue;
1149  }
1150 
1151  # skip anything that isn't a directory
1152  if (!is_dir($InterfacePath))
1153  {
1154  continue;
1155  }
1156 
1157  # read the UI name (if available)
1158  $UIName = @file_get_contents($InterfacePath."/NAME");
1159 
1160  # use the directory name if the UI name isn't available
1161  if ($UIName === FALSE || !strlen($UIName))
1162  {
1163  $UIName = $DirEntry;
1164  }
1165 
1166  $Interfaces[$InterfacePath] = $UIName;
1167  }
1168 
1169  $Dir->close();
1170  }
1171 
1172  # return list to caller
1173  return $Interfaces;
1174  }
1175 
1191  function AddPostProcessingCall($FunctionName,
1192  &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
1193  &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
1194  &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
1195  {
1196  $FuncIndex = count($this->PostProcessingFuncs);
1197  $this->PostProcessingFuncs[$FuncIndex]["FunctionName"] = $FunctionName;
1198  $this->PostProcessingFuncs[$FuncIndex]["Arguments"] = array();
1199  $Index = 1;
1200  while (isset(${"Arg".$Index}) && (${"Arg".$Index} !== self::NOVALUE))
1201  {
1202  $this->PostProcessingFuncs[$FuncIndex]["Arguments"][$Index]
1203  =& ${"Arg".$Index};
1204  $Index++;
1205  }
1206  }
1207 
1213  function AddEnvInclude($FileName)
1214  {
1215  $this->EnvIncludes[] = $FileName;
1216  }
1217 
1224  function GUIFile($FileName)
1225  {
1226  # determine if the file is an image or JavaScript file
1227  $FileIsImage = preg_match("/\.(gif|jpg|png)$/", $FileName);
1228  $FileIsJavascript = preg_match("/\.js$/", $FileName);
1229 
1230  # determine which location to search based on file suffix
1231  $DirList = $FileIsImage ? $this->ImageDirList : $this->IncludeDirList;
1232 
1233  # if directed to get a minimized JavaScript file
1234  if ($FileIsJavascript && $this->UseMinimizedJavascript)
1235  {
1236  # first try to find the minimized JavaScript file
1237  $MinimizedFileName = substr_replace($FileName, ".min", -3, 0);
1238  $FoundFileName = $this->FindFile($DirList, $MinimizedFileName);
1239 
1240  # search for the regular file if a minimized file wasn't found
1241  if (is_null($FoundFileName))
1242  {
1243  $FoundFileName = $this->FindFile($DirList, $FileName);
1244  }
1245  }
1246 
1247  # otherwise just search for the file
1248  else
1249  {
1250  $FoundFileName = $this->FindFile($DirList, $FileName);
1251  }
1252 
1253  # add non-image files to list of found files (used for required files loading)
1254  if (!$FileIsImage) { $this->FoundUIFiles[] = basename($FoundFileName); }
1255 
1256  # return file name to caller
1257  return $FoundFileName;
1258  }
1259 
1269  function PUIFile($FileName)
1270  {
1271  $FullFileName = $this->GUIFile($FileName);
1272  if ($FullFileName) { print($FullFileName); }
1273  }
1274 
1282  function RequireUIFile($FileName)
1283  {
1284  $this->AdditionalRequiredUIFiles[] = $FileName;
1285  }
1286 
1295  function LoadFunction($Callback)
1296  {
1297  # if specified function is not currently available
1298  if (!is_callable($Callback))
1299  {
1300  # if function info looks legal
1301  if (is_string($Callback) && strlen($Callback))
1302  {
1303  # start with function directory list
1304  $Locations = $this->FunctionDirList;
1305 
1306  # add object directories to list
1307  $Locations = array_merge(
1308  $Locations, array_keys(self::$ObjectDirectories));
1309 
1310  # look for function file
1311  $FunctionFileName = $this->FindFile($Locations, "F-".$Callback,
1312  array("php", "html"));
1313 
1314  # if function file was found
1315  if ($FunctionFileName)
1316  {
1317  # load function file
1318  include_once($FunctionFileName);
1319  }
1320  else
1321  {
1322  # log error indicating function load failed
1323  $this->LogError(self::LOGLVL_ERROR, "Unable to load function"
1324  ." for callback \"".$Callback."\".");
1325  }
1326  }
1327  else
1328  {
1329  # log error indicating specified function info was bad
1330  $this->LogError(self::LOGLVL_ERROR, "Unloadable callback value"
1331  ." (".$Callback.")"
1332  ." passed to AF::LoadFunction().");
1333  }
1334  }
1335 
1336  # report to caller whether function load succeeded
1337  return is_callable($Callback);
1338  }
1339 
1345  {
1346  return microtime(TRUE) - $this->ExecutionStartTime;
1347  }
1348 
1354  {
1355  return ini_get("max_execution_time") - $this->GetElapsedExecutionTime();
1356  }
1357 
1363  function GetFreeMemory()
1364  {
1365  $Str = strtoupper(ini_get("memory_limit"));
1366  if (substr($Str, -1) == "B") { $Str = substr($Str, 0, strlen($Str) - 1); }
1367  switch (substr($Str, -1))
1368  {
1369  case "K": $MemoryLimit = (int)$Str * 1024; break;
1370  case "M": $MemoryLimit = (int)$Str * 1048576; break;
1371  case "G": $MemoryLimit = (int)$Str * 1073741824; break;
1372  default: $MemoryLimit = (int)$Str; break;
1373  }
1374 
1375  return $MemoryLimit - memory_get_usage();
1376  }
1377 
1388  function LogSlowPageLoads($NewValue = DB_NOVALUE)
1389  {
1390  return $this->UpdateSetting("LogSlowPageLoads", $NewValue);
1391  }
1392 
1400  function SlowPageLoadThreshold($NewValue = DB_NOVALUE)
1401  {
1402  return $this->UpdateSetting("SlowPageLoadThreshold", $NewValue);
1403  }
1404 
1409  function HtaccessSupport()
1410  {
1411  # HTACCESS_SUPPORT is set in the .htaccess file
1412  return isset($_SERVER["HTACCESS_SUPPORT"]);
1413  }
1414 
1419  static function BaseUrl()
1420  {
1421  $Protocol = (isset($_SERVER["HTTPS"]) ? "https" : "http");
1422  $BaseUrl = $Protocol."://"
1423  .(($_SERVER["SERVER_NAME"] != "127.0.0.1")
1424  ? $_SERVER["SERVER_NAME"]
1425  : $_SERVER["HTTP_HOST"])
1426  .dirname($_SERVER["SCRIPT_NAME"]);
1427  if (substr($BaseUrl, -1) != "/") { $BaseUrl .= "/"; }
1428  return $BaseUrl;
1429  }
1430 
1435  static function FullUrl()
1436  {
1437  $Protocol = (isset($_SERVER["HTTPS"]) ? "https" : "http");
1438  $FullUrl = $Protocol."://"
1439  .(($_SERVER["SERVER_NAME"] != "127.0.0.1")
1440  ? $_SERVER["SERVER_NAME"]
1441  : $_SERVER["HTTP_HOST"])
1442  .$_SERVER["REQUEST_URI"];
1443  return $FullUrl;
1444  }
1445 
1450  static function BasePath()
1451  {
1452  $BasePath = dirname($_SERVER["SCRIPT_NAME"]);
1453 
1454  if (substr($BasePath, -1) != "/")
1455  {
1456  $BasePath .= "/";
1457  }
1458 
1459  return $BasePath;
1460  }
1461 
1467  static function GetScriptUrl()
1468  {
1469  if (array_key_exists("SCRIPT_URL", $_SERVER))
1470  {
1471  return $_SERVER["SCRIPT_URL"];
1472  }
1473  elseif (array_key_exists("REDIRECT_URL", $_SERVER))
1474  {
1475  return $_SERVER["REDIRECT_URL"];
1476  }
1477  elseif (array_key_exists("REQUEST_URI", $_SERVER))
1478  {
1479  $Pieces = parse_url($_SERVER["REQUEST_URI"]);
1480  return $Pieces["path"];
1481  }
1482  else
1483  {
1484  return NULL;
1485  }
1486  }
1487 
1496  static function WasUrlRewritten($ScriptName="index.php")
1497  {
1498  # needed to get the path of the URL minus the query and fragment pieces
1499  $Components = parse_url(self::GetScriptUrl());
1500 
1501  # if parsing was successful and a path is set
1502  if (is_array($Components) && isset($Components["path"]))
1503  {
1504  $BasePath = self::BasePath();
1505  $Path = $Components["path"];
1506 
1507  # the URL was rewritten if the path isn't the base path, i.e., the
1508  # home page, and the file in the URL isn't the script generating the
1509  # page
1510  if ($BasePath != $Path && basename($Path) != $ScriptName)
1511  {
1512  return TRUE;
1513  }
1514  }
1515 
1516  # the URL wasn't rewritten
1517  return FALSE;
1518  }
1519 
1533  function LogError($Level, $Msg)
1534  {
1535  # if error level is at or below current logging level
1536  if ($this->Settings["LoggingLevel"] >= $Level)
1537  {
1538  # attempt to log error message
1539  $Result = $this->LogMessage($Level, $Msg);
1540 
1541  # if logging attempt failed and level indicated significant error
1542  if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
1543  {
1544  # throw exception about inability to log error
1545  static $AlreadyThrewException = FALSE;
1546  if (!$AlreadyThrewException)
1547  {
1548  $AlreadyThrewException = TRUE;
1549  throw new Exception("Unable to log error (".$Level.": ".$Msg.").");
1550  }
1551  }
1552 
1553  # report to caller whether message was logged
1554  return $Result;
1555  }
1556  else
1557  {
1558  # report to caller that message was not logged
1559  return FALSE;
1560  }
1561  }
1562 
1574  function LogMessage($Level, $Msg)
1575  {
1576  # if message level is at or below current logging level
1577  if ($this->Settings["LoggingLevel"] >= $Level)
1578  {
1579  # attempt to open log file
1580  $FHndl = @fopen($this->LogFileName, "a");
1581 
1582  # if log file could not be open
1583  if ($FHndl === FALSE)
1584  {
1585  # report to caller that message was not logged
1586  return FALSE;
1587  }
1588  else
1589  {
1590  # format log entry
1591  $ErrorAbbrevs = array(
1592  self::LOGLVL_FATAL => "FTL",
1593  self::LOGLVL_ERROR => "ERR",
1594  self::LOGLVL_WARNING => "WRN",
1595  self::LOGLVL_INFO => "INF",
1596  self::LOGLVL_DEBUG => "DBG",
1597  self::LOGLVL_TRACE => "TRC",
1598  );
1599  $LogEntry = date("Y-m-d H:i:s")
1600  ." ".($this->RunningInBackground ? "B" : "F")
1601  ." ".$ErrorAbbrevs[$Level]
1602  ." ".$Msg;
1603 
1604  # write entry to log
1605  $Success = fwrite($FHndl, $LogEntry."\n");
1606 
1607  # close log file
1608  fclose($FHndl);
1609 
1610  # report to caller whether message was logged
1611  return ($Success === FALSE) ? FALSE : TRUE;
1612  }
1613  }
1614  else
1615  {
1616  # report to caller that message was not logged
1617  return FALSE;
1618  }
1619  }
1620 
1642  function LoggingLevel($NewValue = DB_NOVALUE)
1643  {
1644  # constrain new level (if supplied) to within legal bounds
1645  if ($NewValue !== DB_NOVALUE)
1646  {
1647  $NewValue = max(min($NewValue, 6), 1);
1648  }
1649 
1650  # set new logging level (if supplied) and return current level to caller
1651  return $this->UpdateSetting("LoggingLevel", $NewValue);
1652  }
1653 
1660  function LogFile($NewValue = NULL)
1661  {
1662  if ($NewValue !== NULL) { $this->LogFileName = $NewValue; }
1663  return $this->LogFileName;
1664  }
1665 
1670  const LOGLVL_TRACE = 6;
1675  const LOGLVL_DEBUG = 5;
1681  const LOGLVL_INFO = 4;
1686  const LOGLVL_WARNING = 3;
1692  const LOGLVL_ERROR = 2;
1697  const LOGLVL_FATAL = 1;
1698 
1699  /*@)*/ /* Application Framework */
1700 
1701  # ---- Event Handling ----------------------------------------------------
1702  /*@(*/
1704 
1714  const EVENTTYPE_CHAIN = 2;
1720  const EVENTTYPE_FIRST = 3;
1728  const EVENTTYPE_NAMED = 4;
1729 
1731  const ORDER_FIRST = 1;
1733  const ORDER_MIDDLE = 2;
1735  const ORDER_LAST = 3;
1736 
1745  function RegisterEvent($EventsOrEventName, $EventType = NULL)
1746  {
1747  # convert parameters to array if not already in that form
1748  $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1749  : array($EventsOrEventName => $Type);
1750 
1751  # for each event
1752  foreach ($Events as $Name => $Type)
1753  {
1754  # store event information
1755  $this->RegisteredEvents[$Name]["Type"] = $Type;
1756  $this->RegisteredEvents[$Name]["Hooks"] = array();
1757  }
1758  }
1759 
1766  function IsRegisteredEvent($EventName)
1767  {
1768  return array_key_exists($EventName, $this->RegisteredEvents)
1769  ? TRUE : FALSE;
1770  }
1771 
1778  function IsHookedEvent($EventName)
1779  {
1780  # the event isn't hooked to if it isn't even registered
1781  if (!$this->IsRegisteredEvent($EventName))
1782  {
1783  return FALSE;
1784  }
1785 
1786  # return TRUE if there is at least one callback hooked to the event
1787  return count($this->RegisteredEvents[$EventName]["Hooks"]) > 0;
1788  }
1789 
1803  function HookEvent($EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
1804  {
1805  # convert parameters to array if not already in that form
1806  $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1807  : array($EventsOrEventName => $Callback);
1808 
1809  # for each event
1810  $Success = TRUE;
1811  foreach ($Events as $EventName => $EventCallback)
1812  {
1813  # if callback is valid
1814  if (is_callable($EventCallback))
1815  {
1816  # if this is a periodic event we process internally
1817  if (isset($this->PeriodicEvents[$EventName]))
1818  {
1819  # process event now
1820  $this->ProcessPeriodicEvent($EventName, $EventCallback);
1821  }
1822  # if specified event has been registered
1823  elseif (isset($this->RegisteredEvents[$EventName]))
1824  {
1825  # add callback for event
1826  $this->RegisteredEvents[$EventName]["Hooks"][]
1827  = array("Callback" => $EventCallback, "Order" => $Order);
1828 
1829  # sort callbacks by order
1830  if (count($this->RegisteredEvents[$EventName]["Hooks"]) > 1)
1831  {
1832  usort($this->RegisteredEvents[$EventName]["Hooks"],
1833  array("ApplicationFramework", "HookEvent_OrderCompare"));
1834  }
1835  }
1836  else
1837  {
1838  $Success = FALSE;
1839  }
1840  }
1841  else
1842  {
1843  $Success = FALSE;
1844  }
1845  }
1846 
1847  # report to caller whether all callbacks were hooked
1848  return $Success;
1849  }
1851  private static function HookEvent_OrderCompare($A, $B)
1852  {
1853  if ($A["Order"] == $B["Order"]) { return 0; }
1854  return ($A["Order"] < $B["Order"]) ? -1 : 1;
1855  }
1856 
1865  function SignalEvent($EventName, $Parameters = NULL)
1866  {
1867  $ReturnValue = NULL;
1868 
1869  # if event has been registered
1870  if (isset($this->RegisteredEvents[$EventName]))
1871  {
1872  # set up default return value (if not NULL)
1873  switch ($this->RegisteredEvents[$EventName]["Type"])
1874  {
1875  case self::EVENTTYPE_CHAIN:
1876  $ReturnValue = $Parameters;
1877  break;
1878 
1879  case self::EVENTTYPE_NAMED:
1880  $ReturnValue = array();
1881  break;
1882  }
1883 
1884  # for each callback for this event
1885  foreach ($this->RegisteredEvents[$EventName]["Hooks"] as $Hook)
1886  {
1887  # invoke callback
1888  $Callback = $Hook["Callback"];
1889  $Result = ($Parameters !== NULL)
1890  ? call_user_func_array($Callback, $Parameters)
1891  : call_user_func($Callback);
1892 
1893  # process return value based on event type
1894  switch ($this->RegisteredEvents[$EventName]["Type"])
1895  {
1896  case self::EVENTTYPE_CHAIN:
1897  if ($Result !== NULL)
1898  {
1899  foreach ($Parameters as $Index => $Value)
1900  {
1901  if (array_key_exists($Index, $Result))
1902  {
1903  $Parameters[$Index] = $Result[$Index];
1904  }
1905  }
1906  $ReturnValue = $Parameters;
1907  }
1908  break;
1909 
1910  case self::EVENTTYPE_FIRST:
1911  if ($Result !== NULL)
1912  {
1913  $ReturnValue = $Result;
1914  break 2;
1915  }
1916  break;
1917 
1918  case self::EVENTTYPE_NAMED:
1919  $CallbackName = is_array($Callback)
1920  ? (is_object($Callback[0])
1921  ? get_class($Callback[0])
1922  : $Callback[0])."::".$Callback[1]
1923  : $Callback;
1924  $ReturnValue[$CallbackName] = $Result;
1925  break;
1926 
1927  default:
1928  break;
1929  }
1930  }
1931  }
1932  else
1933  {
1934  $this->LogError(self::LOGLVL_WARNING,
1935  "Unregistered event signaled (".$EventName.").");
1936  }
1937 
1938  # return value if any to caller
1939  return $ReturnValue;
1940  }
1941 
1947  function IsStaticOnlyEvent($EventName)
1948  {
1949  return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
1950  }
1951 
1962  function EventWillNextRunAt($EventName, $Callback)
1963  {
1964  # if event is not a periodic event report failure to caller
1965  if (!array_key_exists($EventName, $this->EventPeriods)) { return FALSE; }
1966 
1967  # retrieve last execution time for event if available
1968  $Signature = self::GetCallbackSignature($Callback);
1969  $LastRunTime = $this->DB->Query("SELECT LastRunAt FROM PeriodicEvents"
1970  ." WHERE Signature = '".addslashes($Signature)."'", "LastRunAt");
1971 
1972  # if event was not found report failure to caller
1973  if ($LastRunTime === NULL) { return FALSE; }
1974 
1975  # calculate next run time based on event period
1976  $NextRunTime = strtotime($LastRunTime) + $this->EventPeriods[$EventName];
1977 
1978  # report next run time to caller
1979  return $NextRunTime;
1980  }
1981 
1998  {
1999  # retrieve last execution times
2000  $this->DB->Query("SELECT * FROM PeriodicEvents");
2001  $LastRunTimes = $this->DB->FetchColumn("LastRunAt", "Signature");
2002 
2003  # for each known event
2004  $Events = array();
2005  foreach ($this->KnownPeriodicEvents as $Signature => $Info)
2006  {
2007  # if last run time for event is available
2008  if (array_key_exists($Signature, $LastRunTimes))
2009  {
2010  # calculate next run time for event
2011  $LastRun = strtotime($LastRunTimes[$Signature]);
2012  $NextRun = $LastRun + $this->EventPeriods[$Info["Period"]];
2013  if ($Info["Period"] == "EVENT_PERIODIC") { $LastRun = FALSE; }
2014  }
2015  else
2016  {
2017  # set info to indicate run times are not known
2018  $LastRun = FALSE;
2019  $NextRun = FALSE;
2020  }
2021 
2022  # add event info to list
2023  $Events[$Signature] = $Info;
2024  $Events[$Signature]["LastRun"] = $LastRun;
2025  $Events[$Signature]["NextRun"] = $NextRun;
2026  $Events[$Signature]["Parameters"] = NULL;
2027  }
2028 
2029  # return list of known events to caller
2030  return $Events;
2031  }
2032 
2033  /*@)*/ /* Event Handling */
2034 
2035  # ---- Task Management ---------------------------------------------------
2036  /*@(*/
2038 
2040  const PRIORITY_HIGH = 1;
2042  const PRIORITY_MEDIUM = 2;
2044  const PRIORITY_LOW = 3;
2047 
2060  function QueueTask($Callback, $Parameters = NULL,
2061  $Priority = self::PRIORITY_LOW, $Description = "")
2062  {
2063  # pack task info and write to database
2064  if ($Parameters === NULL) { $Parameters = array(); }
2065  $this->DB->Query("INSERT INTO TaskQueue"
2066  ." (Callback, Parameters, Priority, Description)"
2067  ." VALUES ('".addslashes(serialize($Callback))."', '"
2068  .addslashes(serialize($Parameters))."', ".intval($Priority).", '"
2069  .addslashes($Description)."')");
2070  }
2071 
2089  function QueueUniqueTask($Callback, $Parameters = NULL,
2090  $Priority = self::PRIORITY_LOW, $Description = "")
2091  {
2092  if ($this->TaskIsInQueue($Callback, $Parameters))
2093  {
2094  $QueryResult = $this->DB->Query("SELECT TaskId,Priority FROM TaskQueue"
2095  ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2096  .($Parameters ? " AND Parameters = '"
2097  .addslashes(serialize($Parameters))."'" : ""));
2098  if ($QueryResult !== FALSE)
2099  {
2100  $Record = $this->DB->FetchRow();
2101  if ($Record["Priority"] > $Priority)
2102  {
2103  $this->DB->Query("UPDATE TaskQueue"
2104  ." SET Priority = ".intval($Priority)
2105  ." WHERE TaskId = ".intval($Record["TaskId"]));
2106  }
2107  }
2108  return FALSE;
2109  }
2110  else
2111  {
2112  $this->QueueTask($Callback, $Parameters, $Priority, $Description);
2113  return TRUE;
2114  }
2115  }
2116 
2126  function TaskIsInQueue($Callback, $Parameters = NULL)
2127  {
2128  $QueuedCount = $this->DB->Query(
2129  "SELECT COUNT(*) AS FoundCount FROM TaskQueue"
2130  ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2131  .($Parameters ? " AND Parameters = '"
2132  .addslashes(serialize($Parameters))."'" : ""),
2133  "FoundCount");
2134  $RunningCount = $this->DB->Query(
2135  "SELECT COUNT(*) AS FoundCount FROM RunningTasks"
2136  ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2137  .($Parameters ? " AND Parameters = '"
2138  .addslashes(serialize($Parameters))."'" : ""),
2139  "FoundCount");
2140  $FoundCount = $QueuedCount + $RunningCount;
2141  return ($FoundCount ? TRUE : FALSE);
2142  }
2143 
2149  function GetTaskQueueSize($Priority = NULL)
2150  {
2151  return $this->GetQueuedTaskCount(NULL, NULL, $Priority);
2152  }
2153 
2161  function GetQueuedTaskList($Count = 100, $Offset = 0)
2162  {
2163  return $this->GetTaskList("SELECT * FROM TaskQueue"
2164  ." ORDER BY Priority, TaskId ", $Count, $Offset);
2165  }
2166 
2180  function GetQueuedTaskCount($Callback = NULL,
2181  $Parameters = NULL, $Priority = NULL, $Description = NULL)
2182  {
2183  $Query = "SELECT COUNT(*) AS TaskCount FROM TaskQueue";
2184  $Sep = " WHERE";
2185  if ($Callback !== NULL)
2186  {
2187  $Query .= $Sep." Callback = '".addslashes(serialize($Callback))."'";
2188  $Sep = " AND";
2189  }
2190  if ($Parameters !== NULL)
2191  {
2192  $Query .= $Sep." Parameters = '".addslashes(serialize($Parameters))."'";
2193  $Sep = " AND";
2194  }
2195  if ($Priority !== NULL)
2196  {
2197  $Query .= $Sep." Priority = ".intval($Priority);
2198  $Sep = " AND";
2199  }
2200  if ($Description !== NULL)
2201  {
2202  $Query .= $Sep." Description = '".addslashes($Description)."'";
2203  }
2204  return $this->DB->Query($Query, "TaskCount");
2205  }
2206 
2214  function GetRunningTaskList($Count = 100, $Offset = 0)
2215  {
2216  return $this->GetTaskList("SELECT * FROM RunningTasks"
2217  ." WHERE StartedAt >= '".date("Y-m-d H:i:s",
2218  (time() - ini_get("max_execution_time")))."'"
2219  ." ORDER BY StartedAt", $Count, $Offset);
2220  }
2221 
2229  function GetOrphanedTaskList($Count = 100, $Offset = 0)
2230  {
2231  return $this->GetTaskList("SELECT * FROM RunningTasks"
2232  ." WHERE StartedAt < '".date("Y-m-d H:i:s",
2233  (time() - ini_get("max_execution_time")))."'"
2234  ." ORDER BY StartedAt", $Count, $Offset);
2235  }
2236 
2242  {
2243  return $this->DB->Query("SELECT COUNT(*) AS Count FROM RunningTasks"
2244  ." WHERE StartedAt < '".date("Y-m-d H:i:s",
2245  (time() - ini_get("max_execution_time")))."'",
2246  "Count");
2247  }
2248 
2254  function ReQueueOrphanedTask($TaskId, $NewPriority = NULL)
2255  {
2256  $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
2257  $this->DB->Query("INSERT INTO TaskQueue"
2258  ." (Callback,Parameters,Priority,Description) "
2259  ."SELECT Callback, Parameters, Priority, Description"
2260  ." FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2261  if ($NewPriority !== NULL)
2262  {
2263  $NewTaskId = $this->DB->LastInsertId();
2264  $this->DB->Query("UPDATE TaskQueue SET Priority = "
2265  .intval($NewPriority)
2266  ." WHERE TaskId = ".intval($NewTaskId));
2267  }
2268  $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2269  $this->DB->Query("UNLOCK TABLES");
2270  }
2271 
2276  function DeleteTask($TaskId)
2277  {
2278  $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2279  $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2280  }
2281 
2289  function GetTask($TaskId)
2290  {
2291  # assume task will not be found
2292  $Task = NULL;
2293 
2294  # look for task in task queue
2295  $this->DB->Query("SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2296 
2297  # if task was not found in queue
2298  if (!$this->DB->NumRowsSelected())
2299  {
2300  # look for task in running task list
2301  $this->DB->Query("SELECT * FROM RunningTasks WHERE TaskId = "
2302  .intval($TaskId));
2303  }
2304 
2305  # if task was found
2306  if ($this->DB->NumRowsSelected())
2307  {
2308  # if task was periodic
2309  $Row = $this->DB->FetchRow();
2310  if ($Row["Callback"] ==
2311  serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
2312  {
2313  # unpack periodic task callback
2314  $WrappedCallback = unserialize($Row["Parameters"]);
2315  $Task["Callback"] = $WrappedCallback[1];
2316  $Task["Parameters"] = $WrappedCallback[2];
2317  }
2318  else
2319  {
2320  # unpack task callback and parameters
2321  $Task["Callback"] = unserialize($Row["Callback"]);
2322  $Task["Parameters"] = unserialize($Row["Parameters"]);
2323  }
2324  }
2325 
2326  # return task to caller
2327  return $Task;
2328  }
2329 
2337  function TaskExecutionEnabled($NewValue = DB_NOVALUE)
2338  {
2339  return $this->UpdateSetting("TaskExecutionEnabled", $NewValue);
2340  }
2341 
2347  function MaxTasks($NewValue = DB_NOVALUE)
2348  {
2349  return $this->UpdateSetting("MaxTasksRunning", $NewValue);
2350  }
2351 
2359  function MaxExecutionTime($NewValue = NULL)
2360  {
2361  if (func_num_args() && !ini_get("safe_mode"))
2362  {
2363  if ($NewValue != $this->Settings["MaxExecTime"])
2364  {
2365  $this->Settings["MaxExecTime"] = max($NewValue, 5);
2366  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
2367  ." SET MaxExecTime = '"
2368  .intval($this->Settings["MaxExecTime"])."'");
2369  }
2370  ini_set("max_execution_time", $this->Settings["MaxExecTime"]);
2371  set_time_limit($this->Settings["MaxExecTime"]);
2372  }
2373  return ini_get("max_execution_time");
2374  }
2375 
2376  /*@)*/ /* Task Management */
2377 
2378  # ---- Backward Compatibility --------------------------------------------
2379  /*@(*/
2381 
2386  function FindCommonTemplate($BaseName)
2387  {
2388  return $this->FindFile(
2389  $this->IncludeDirList, $BaseName, array("tpl", "html"));
2390  }
2391 
2392  /*@)*/ /* Backward Compatibility */
2393 
2394 
2395  # ---- PRIVATE INTERFACE -------------------------------------------------
2396 
2397  private $ActiveUI = "default";
2398  private $BrowserDetectFunc;
2399  private $CleanUrlMappings = array();
2400  private $CleanUrlRewritePerformed = FALSE;
2401  private $DB;
2402  private $DefaultPage = "Home";
2403  private $EnvIncludes = array();
2404  private $ExecutionStartTime;
2405  private $FoundUIFiles = array();
2406  private $AdditionalRequiredUIFiles = array();
2407  private $HtmlCharset = "UTF-8";
2408  private $JumpToPage = NULL;
2409  private $LogFileName = "local/logs/site.log";
2410  private $MaxRunningTasksToTrack = 250;
2411  private $OutputModificationPatterns = array();
2412  private $OutputModificationReplacements = array();
2413  private $OutputModificationCallbacks = array();
2414  private $OutputModificationCallbackInfo;
2415  private $PageName;
2416  private $PostProcessingFuncs = array();
2417  private $RunningInBackground = FALSE;
2418  private $RunningTask;
2419  private $Settings;
2420  private $SuppressHTML = FALSE;
2421  private $SaveTemplateLocationCache = FALSE;
2422  private $UnbufferedCallbacks = array();
2423  private $UseBaseTag = FALSE;
2424  private $UseMinimizedJavascript = FALSE;
2425 
2426  private static $AppName = "ScoutAF";
2427  private static $ObjectDirectories = array();
2428  private static $ObjectLocationCache;
2429  private static $ObjectLocationCacheInterval = 60;
2430  private static $ObjectLocationCacheExpiration;
2431  private static $SaveObjectLocationCache = FALSE;
2432  private static $SessionLifetime = 1440;
2433 
2438  private $NoTSR = FALSE;
2439 
2440  private $KnownPeriodicEvents = array();
2441  private $PeriodicEvents = array(
2442  "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
2443  "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
2444  "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
2445  "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
2446  "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
2447  );
2448  private $EventPeriods = array(
2449  "EVENT_HOURLY" => 3600,
2450  "EVENT_DAILY" => 86400,
2451  "EVENT_WEEKLY" => 604800,
2452  "EVENT_MONTHLY" => 2592000,
2453  "EVENT_PERIODIC" => 0,
2454  );
2455  private $UIEvents = array(
2456  "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
2457  "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
2458  "EVENT_PHP_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
2459  "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
2460  "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
2461  "EVENT_PAGE_OUTPUT_FILTER" => self::EVENTTYPE_CHAIN,
2462  );
2463 
2467  private function LoadSettings()
2468  {
2469  # read settings in from database
2470  $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
2471  $this->Settings = $this->DB->FetchRow();
2472 
2473  # if settings were not previously initialized
2474  if (!$this->Settings)
2475  {
2476  # initialize settings in database
2477  $this->DB->Query("INSERT INTO ApplicationFrameworkSettings"
2478  ." (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
2479 
2480  # read new settings in from database
2481  $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
2482  $this->Settings = $this->DB->FetchRow();
2483  }
2484 
2485  # if base path was not previously set or we appear to have moved
2486  if (!array_key_exists("BasePath", $this->Settings)
2487  || (!strlen($this->Settings["BasePath"]))
2488  || (!array_key_exists("BasePathCheck", $this->Settings))
2489  || (__FILE__ != $this->Settings["BasePathCheck"]))
2490  {
2491  # attempt to extract base path from Apache .htaccess file
2492  if (is_readable(".htaccess"))
2493  {
2494  $Lines = file(".htaccess");
2495  foreach ($Lines as $Line)
2496  {
2497  if (preg_match("/\\s*RewriteBase\\s+/", $Line))
2498  {
2499  $Pieces = preg_split(
2500  "/\\s+/", $Line, NULL, PREG_SPLIT_NO_EMPTY);
2501  $BasePath = $Pieces[1];
2502  }
2503  }
2504  }
2505 
2506  # if base path was found
2507  if (isset($BasePath))
2508  {
2509  # save base path locally
2510  $this->Settings["BasePath"] = $BasePath;
2511 
2512  # save base path to database
2513  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
2514  ." SET BasePath = '".addslashes($BasePath)."'"
2515  .", BasePathCheck = '".addslashes(__FILE__)."'");
2516  }
2517  }
2518 
2519  # if template location cache has been saved to database
2520  if (isset($this->Settings["TemplateLocationCache"]))
2521  {
2522  # unserialize cache values into array and use if valid
2523  $Cache = unserialize($this->Settings["TemplateLocationCache"]);
2524  $this->Settings["TemplateLocationCache"] =
2525  count($Cache) ? $Cache : array();
2526  }
2527  else
2528  {
2529  # start with empty cache
2530  $this->Settings["TemplateLocationCache"] = array();
2531  }
2532 
2533  # if object location cache has been saved to database
2534  if (isset($this->Settings["ObjectLocationCache"]))
2535  {
2536  # unserialize cache values into array and use if valid
2537  $Cache = unserialize($this->Settings["ObjectLocationCache"]);
2538  $this->Settings["ObjectLocationCache"] =
2539  count($Cache) ? $Cache : array();
2540 
2541  # store static versions for use when autoloading objects
2542  self::$ObjectLocationCache =
2543  $this->Settings["ObjectLocationCache"];
2544  self::$ObjectLocationCacheInterval =
2545  $this->Settings["ObjectLocationCacheInterval"];
2546  self::$ObjectLocationCacheExpiration =
2547  $this->Settings["ObjectLocationCacheExpiration"];
2548  }
2549  else
2550  {
2551  # start with empty cache
2552  $this->Settings["ObjectLocationCache"] = array();
2553  }
2554  }
2555 
2562  private function RewriteCleanUrls($PageName)
2563  {
2564  # if URL rewriting is supported by the server
2565  if ($this->HtaccessSupport())
2566  {
2567  # retrieve current URL and remove base path if present
2568  $Url = $this->GetPageLocation();
2569 
2570  # for each clean URL mapping
2571  foreach ($this->CleanUrlMappings as $Info)
2572  {
2573  # if current URL matches clean URL pattern
2574  if (preg_match($Info["Pattern"], $Url, $Matches))
2575  {
2576  # set new page
2577  $PageName = $Info["Page"];
2578 
2579  # if $_GET variables specified for clean URL
2580  if ($Info["GetVars"] !== NULL)
2581  {
2582  # for each $_GET variable specified for clean URL
2583  foreach ($Info["GetVars"] as $VarName => $VarTemplate)
2584  {
2585  # start with template for variable value
2586  $Value = $VarTemplate;
2587 
2588  # for each subpattern matched in current URL
2589  foreach ($Matches as $Index => $Match)
2590  {
2591  # if not first (whole) match
2592  if ($Index > 0)
2593  {
2594  # make any substitutions in template
2595  $Value = str_replace("$".$Index, $Match, $Value);
2596  }
2597  }
2598 
2599  # set $_GET variable
2600  $_GET[$VarName] = $Value;
2601  }
2602  }
2603 
2604  # set flag indicating clean URL mapped
2605  $this->CleanUrlRewritePerformed = TRUE;
2606 
2607  # stop looking for a mapping
2608  break;
2609  }
2610  }
2611  }
2612 
2613  # return (possibly) updated page name to caller
2614  return $PageName;
2615  }
2616 
2633  private function FindFile($DirectoryList, $BaseName,
2634  $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
2635  {
2636  # generate template cache index for this page
2637  $CacheIndex = md5(serialize($DirectoryList))
2638  .":".$this->ActiveUI.":".$BaseName;
2639 
2640  # if we have cached location and cache expiration time has not elapsed
2641  if (($this->Settings["TemplateLocationCacheInterval"] > 0)
2642  && count($this->Settings["TemplateLocationCache"])
2643  && array_key_exists($CacheIndex,
2644  $this->Settings["TemplateLocationCache"])
2645  && (time() < strtotime(
2646  $this->Settings["TemplateLocationCacheExpiration"])))
2647  {
2648  # use template location from cache
2649  $FoundFileName = $this->Settings[
2650  "TemplateLocationCache"][$CacheIndex];
2651  }
2652  else
2653  {
2654  # if suffixes specified and base name does not include suffix
2655  if (count($PossibleSuffixes)
2656  && !preg_match("/\.[a-zA-Z0-9]+$/", $BaseName))
2657  {
2658  # add versions of file names with suffixes to file name list
2659  $FileNames = array();
2660  foreach ($PossibleSuffixes as $Suffix)
2661  {
2662  $FileNames[] = $BaseName.".".$Suffix;
2663  }
2664  }
2665  else
2666  {
2667  # use base name as file name
2668  $FileNames = array($BaseName);
2669  }
2670 
2671  # if prefixes specified
2672  if (count($PossiblePrefixes))
2673  {
2674  # add versions of file names with prefixes to file name list
2675  $NewFileNames = array();
2676  foreach ($FileNames as $FileName)
2677  {
2678  foreach ($PossiblePrefixes as $Prefix)
2679  {
2680  $NewFileNames[] = $Prefix.$FileName;
2681  }
2682  }
2683  $FileNames = $NewFileNames;
2684  }
2685 
2686  # for each possible location
2687  $FoundFileName = NULL;
2688  foreach ($DirectoryList as $Dir)
2689  {
2690  # substitute active UI name into path
2691  $Dir = str_replace("%ACTIVEUI%", $this->ActiveUI, $Dir);
2692 
2693  # for each possible file name
2694  foreach ($FileNames as $File)
2695  {
2696  # if template is found at location
2697  if (file_exists($Dir.$File))
2698  {
2699  # save full template file name and stop looking
2700  $FoundFileName = $Dir.$File;
2701  break 2;
2702  }
2703  }
2704  }
2705 
2706  # save location in cache
2707  $this->Settings["TemplateLocationCache"][$CacheIndex]
2708  = $FoundFileName;
2709 
2710  # set flag indicating that cache should be saved
2711  $this->SaveTemplateLocationCache = TRUE;
2712  }
2713 
2714  # return full template file name to caller
2715  return $FoundFileName;
2716  }
2717 
2724  private function GetRequiredFilesNotYetLoaded($PageContentFile)
2725  {
2726  # start out assuming no files required
2727  $RequiredFiles = array();
2728 
2729  # if page content file supplied
2730  if ($PageContentFile)
2731  {
2732  # if file containing list of required files is available
2733  $Path = dirname($PageContentFile);
2734  $RequireListFile = $Path."/REQUIRES";
2735  if (file_exists($RequireListFile))
2736  {
2737  # read in list of required files
2738  $RequestedFiles = file($RequireListFile);
2739 
2740  # for each line in required file list
2741  foreach ($RequestedFiles as $Line)
2742  {
2743  # if line is not a comment
2744  $Line = trim($Line);
2745  if (!preg_match("/^#/", $Line))
2746  {
2747  # if file has not already been loaded
2748  if (!in_array($Line, $this->FoundUIFiles))
2749  {
2750  # add to list of required files
2751  $RequiredFiles[] = $Line;
2752  }
2753  }
2754  }
2755  }
2756  }
2757 
2758  # add in additional required files if any
2759  if (count($this->AdditionalRequiredUIFiles))
2760  {
2761  # make sure there are no duplicates
2762  $AdditionalRequiredUIFiles = array_unique(
2763  $this->AdditionalRequiredUIFiles);
2764 
2765  $RequiredFiles = array_merge(
2766  $RequiredFiles, $AdditionalRequiredUIFiles);
2767  }
2768 
2769  # return list of required files to caller
2770  return $RequiredFiles;
2771  }
2772 
2776  private function SetUpObjectAutoloading()
2777  {
2779  function __autoload($ClassName)
2780  {
2781  ApplicationFramework::AutoloadObjects($ClassName);
2782  }
2783  }
2784 
2790  static function AutoloadObjects($ClassName)
2791  {
2792  # if caching is not turned off
2793  # and we have a cached location for class
2794  # and cache expiration has not elapsed
2795  # and file at cached location is readable
2796  if ((self::$ObjectLocationCacheInterval > 0)
2797  && count(self::$ObjectLocationCache)
2798  && array_key_exists($ClassName,
2799  self::$ObjectLocationCache)
2800  && (time() < strtotime(
2801  self::$ObjectLocationCacheExpiration))
2802  && is_readable(self::$ObjectLocationCache[$ClassName]))
2803  {
2804  # use object location from cache
2805  require_once(self::$ObjectLocationCache[$ClassName]);
2806  }
2807  else
2808  {
2809  # for each possible object file directory
2810  static $FileLists;
2811  foreach (self::$ObjectDirectories as $Location => $Info)
2812  {
2813  # if directory looks valid
2814  if (is_dir($Location))
2815  {
2816  # build class file name
2817  $NewClassName = ($Info["ClassPattern"] && $Info["ClassReplacement"])
2818  ? preg_replace($Info["ClassPattern"],
2819  $Info["ClassReplacement"], $ClassName)
2820  : $ClassName;
2821 
2822  # read in directory contents if not already retrieved
2823  if (!isset($FileLists[$Location]))
2824  {
2825  $FileLists[$Location] = self::ReadDirectoryTree(
2826  $Location, '/^.+\.php$/i');
2827  }
2828 
2829  # for each file in target directory
2830  $FileNames = $FileLists[$Location];
2831  $TargetName = strtolower($Info["Prefix"].$NewClassName.".php");
2832  foreach ($FileNames as $FileName)
2833  {
2834  # if file matches our target object file name
2835  if (strtolower($FileName) == $TargetName)
2836  {
2837  # include object file
2838  require_once($Location.$FileName);
2839 
2840  # save location to cache
2841  self::$ObjectLocationCache[$ClassName]
2842  = $Location.$FileName;
2843 
2844  # set flag indicating that cache should be saved
2845  self::$SaveObjectLocationCache = TRUE;
2846 
2847  # stop looking
2848  break 2;
2849  }
2850  }
2851  }
2852  }
2853  }
2854  }
2855 
2863  private static function ReadDirectoryTree($Directory, $Pattern)
2864  {
2865  $CurrentDir = getcwd();
2866  chdir($Directory);
2867  $DirIter = new RecursiveDirectoryIterator(".");
2868  $IterIter = new RecursiveIteratorIterator($DirIter);
2869  $RegexResults = new RegexIterator($IterIter, $Pattern,
2870  RecursiveRegexIterator::GET_MATCH);
2871  $FileList = array();
2872  foreach ($RegexResults as $Result)
2873  {
2874  $FileList[] = substr($Result[0], 2);
2875  }
2876  chdir($CurrentDir);
2877  return $FileList;
2878  }
2884  private function UndoMagicQuotes()
2885  {
2886  # if this PHP version has magic quotes support
2887  if (version_compare(PHP_VERSION, "5.4.0", "<"))
2888  {
2889  # turn off runtime magic quotes if on
2890  if (get_magic_quotes_runtime())
2891  {
2892  set_magic_quotes_runtime(FALSE);
2893  }
2894 
2895  # if magic quotes GPC is on
2896  if (get_magic_quotes_gpc())
2897  {
2898  # strip added slashes from incoming variables
2899  $GPC = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
2900  array_walk_recursive($GPC,
2901  array($this, "UndoMagicQuotes_StripCallback"));
2902  }
2903  }
2904  }
2905  private function UndoMagicQuotes_StripCallback(&$Value, $Key)
2906  {
2907  $Value = stripslashes($Value);
2908  }
2909 
2914  private function LoadUIFunctions()
2915  {
2916  $Dirs = array(
2917  "local/interface/%ACTIVEUI%/include",
2918  "interface/%ACTIVEUI%/include",
2919  "local/interface/default/include",
2920  "interface/default/include",
2921  );
2922  foreach ($Dirs as $Dir)
2923  {
2924  $Dir = str_replace("%ACTIVEUI%", $this->ActiveUI, $Dir);
2925  if (is_dir($Dir))
2926  {
2927  $FileNames = scandir($Dir);
2928  foreach ($FileNames as $FileName)
2929  {
2930  if (preg_match("/^F-([A-Za-z0-9_]+)\.php/", $FileName, $Matches)
2931  || preg_match("/^F-([A-Za-z0-9_]+)\.html/", $FileName, $Matches))
2932  {
2933  if (!function_exists($Matches[1]))
2934  {
2935  include_once($Dir."/".$FileName);
2936  }
2937  }
2938  }
2939  }
2940  }
2941  }
2942 
2948  private function ProcessPeriodicEvent($EventName, $Callback)
2949  {
2950  # retrieve last execution time for event if available
2951  $Signature = self::GetCallbackSignature($Callback);
2952  $LastRun = $this->DB->Query("SELECT LastRunAt FROM PeriodicEvents"
2953  ." WHERE Signature = '".addslashes($Signature)."'", "LastRunAt");
2954 
2955  # determine whether enough time has passed for event to execute
2956  $ShouldExecute = (($LastRun === NULL)
2957  || (time() > (strtotime($LastRun) + $this->EventPeriods[$EventName])))
2958  ? TRUE : FALSE;
2959 
2960  # if event should run
2961  if ($ShouldExecute)
2962  {
2963  # add event to task queue
2964  $WrapperCallback = array("ApplicationFramework", "PeriodicEventWrapper");
2965  $WrapperParameters = array(
2966  $EventName, $Callback, array("LastRunAt" => $LastRun));
2967  $this->QueueUniqueTask($WrapperCallback, $WrapperParameters);
2968  }
2969 
2970  # add event to list of periodic events
2971  $this->KnownPeriodicEvents[$Signature] = array(
2972  "Period" => $EventName,
2973  "Callback" => $Callback,
2974  "Queued" => $ShouldExecute);
2975  }
2976 
2984  private static function PeriodicEventWrapper($EventName, $Callback, $Parameters)
2985  {
2986  static $DB;
2987  if (!isset($DB)) { $DB = new Database(); }
2988 
2989  # run event
2990  $ReturnVal = call_user_func_array($Callback, $Parameters);
2991 
2992  # if event is already in database
2993  $Signature = self::GetCallbackSignature($Callback);
2994  if ($DB->Query("SELECT COUNT(*) AS EventCount FROM PeriodicEvents"
2995  ." WHERE Signature = '".addslashes($Signature)."'", "EventCount"))
2996  {
2997  # update last run time for event
2998  $DB->Query("UPDATE PeriodicEvents SET LastRunAt = "
2999  .(($EventName == "EVENT_PERIODIC")
3000  ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
3001  : "NOW()")
3002  ." WHERE Signature = '".addslashes($Signature)."'");
3003  }
3004  else
3005  {
3006  # add last run time for event to database
3007  $DB->Query("INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES "
3008  ."('".addslashes($Signature)."', "
3009  .(($EventName == "EVENT_PERIODIC")
3010  ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
3011  : "NOW()").")");
3012  }
3013  }
3014 
3020  private static function GetCallbackSignature($Callback)
3021  {
3022  return !is_array($Callback) ? $Callback
3023  : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
3024  ."::".$Callback[1];
3025  }
3026 
3031  private function PrepForTSR()
3032  {
3033  # if HTML has been output and it's time to launch another task
3034  # (only TSR if HTML has been output because otherwise browsers
3035  # may misbehave after connection is closed)
3036  if (($this->JumpToPage || !$this->SuppressHTML)
3037  && (time() > (strtotime($this->Settings["LastTaskRunAt"])
3038  + (ini_get("max_execution_time")
3039  / $this->Settings["MaxTasksRunning"]) + 5))
3040  && $this->GetTaskQueueSize()
3041  && $this->Settings["TaskExecutionEnabled"])
3042  {
3043  # begin buffering output for TSR
3044  ob_start();
3045 
3046  # let caller know it is time to launch another task
3047  return TRUE;
3048  }
3049  else
3050  {
3051  # let caller know it is not time to launch another task
3052  return FALSE;
3053  }
3054  }
3055 
3060  private function LaunchTSR()
3061  {
3062  # set headers to close out connection to browser
3063  if (!$this->NoTSR)
3064  {
3065  ignore_user_abort(TRUE);
3066  header("Connection: close");
3067  header("Content-Length: ".ob_get_length());
3068  }
3069 
3070  # output buffered content
3071  while (ob_get_level()) { ob_end_flush(); }
3072  flush();
3073 
3074  # write out any outstanding data and end HTTP session
3075  session_write_close();
3076 
3077  # set flag indicating that we are now running in background
3078  $this->RunningInBackground = TRUE;
3079 
3080  # if there is still a task in the queue
3081  if ($this->GetTaskQueueSize())
3082  {
3083  # turn on output buffering to (hopefully) record any crash output
3084  ob_start();
3085 
3086  # lock tables and grab last task run time to double check
3087  $this->DB->Query("LOCK TABLES ApplicationFrameworkSettings WRITE");
3088  $this->LoadSettings();
3089 
3090  # if still time to launch another task
3091  if (time() > (strtotime($this->Settings["LastTaskRunAt"])
3092  + (ini_get("max_execution_time")
3093  / $this->Settings["MaxTasksRunning"]) + 5))
3094  {
3095  # update the "last run" time and release tables
3096  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
3097  ." SET LastTaskRunAt = '".date("Y-m-d H:i:s")."'");
3098  $this->DB->Query("UNLOCK TABLES");
3099 
3100  # run tasks while there is a task in the queue and enough time left
3101  do
3102  {
3103  # run the next task
3104  $this->RunNextTask();
3105  }
3106  while ($this->GetTaskQueueSize()
3107  && ($this->GetSecondsBeforeTimeout() > 65));
3108  }
3109  else
3110  {
3111  # release tables
3112  $this->DB->Query("UNLOCK TABLES");
3113  }
3114  }
3115  }
3116 
3124  private function GetTaskList($DBQuery, $Count, $Offset)
3125  {
3126  $this->DB->Query($DBQuery." LIMIT ".intval($Offset).",".intval($Count));
3127  $Tasks = array();
3128  while ($Row = $this->DB->FetchRow())
3129  {
3130  $Tasks[$Row["TaskId"]] = $Row;
3131  if ($Row["Callback"] ==
3132  serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
3133  {
3134  $WrappedCallback = unserialize($Row["Parameters"]);
3135  $Tasks[$Row["TaskId"]]["Callback"] = $WrappedCallback[1];
3136  $Tasks[$Row["TaskId"]]["Parameters"] = NULL;
3137  }
3138  else
3139  {
3140  $Tasks[$Row["TaskId"]]["Callback"] = unserialize($Row["Callback"]);
3141  $Tasks[$Row["TaskId"]]["Parameters"] = unserialize($Row["Parameters"]);
3142  }
3143  }
3144  return $Tasks;
3145  }
3146 
3150  private function RunNextTask()
3151  {
3152  # lock tables to prevent same task from being run by multiple sessions
3153  $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
3154 
3155  # look for task at head of queue
3156  $this->DB->Query("SELECT * FROM TaskQueue ORDER BY Priority, TaskId LIMIT 1");
3157  $Task = $this->DB->FetchRow();
3158 
3159  # if there was a task available
3160  if ($Task)
3161  {
3162  # move task from queue to running tasks list
3163  $this->DB->Query("INSERT INTO RunningTasks "
3164  ."(TaskId,Callback,Parameters,Priority,Description) "
3165  ."SELECT * FROM TaskQueue WHERE TaskId = "
3166  .intval($Task["TaskId"]));
3167  $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = "
3168  .intval($Task["TaskId"]));
3169 
3170  # release table locks to again allow other sessions to run tasks
3171  $this->DB->Query("UNLOCK TABLES");
3172 
3173  # unpack stored task info
3174  $Callback = unserialize($Task["Callback"]);
3175  $Parameters = unserialize($Task["Parameters"]);
3176 
3177  # attempt to load task callback if not already available
3178  $this->LoadFunction($Callback);
3179 
3180  # run task
3181  $this->RunningTask = $Task;
3182  if ($Parameters)
3183  {
3184  call_user_func_array($Callback, $Parameters);
3185  }
3186  else
3187  {
3188  call_user_func($Callback);
3189  }
3190  unset($this->RunningTask);
3191 
3192  # remove task from running tasks list
3193  $this->DB->Query("DELETE FROM RunningTasks"
3194  ." WHERE TaskId = ".intval($Task["TaskId"]));
3195 
3196  # prune running tasks list if necessary
3197  $RunningTasksCount = $this->DB->Query(
3198  "SELECT COUNT(*) AS TaskCount FROM RunningTasks", "TaskCount");
3199  if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
3200  {
3201  $this->DB->Query("DELETE FROM RunningTasks ORDER BY StartedAt"
3202  ." LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
3203  }
3204  }
3205  else
3206  {
3207  # release table locks to again allow other sessions to run tasks
3208  $this->DB->Query("UNLOCK TABLES");
3209  }
3210  }
3211 
3217  function OnCrash()
3218  {
3219  if (isset($this->RunningTask))
3220  {
3221  if (function_exists("error_get_last"))
3222  {
3223  $CrashInfo["LastError"] = error_get_last();
3224  }
3225  if (ob_get_length() !== FALSE)
3226  {
3227  $CrashInfo["OutputBuffer"] = ob_get_contents();
3228  }
3229  if (isset($CrashInfo))
3230  {
3231  $DB = new Database();
3232  $DB->Query("UPDATE RunningTasks SET CrashInfo = '"
3233  .addslashes(serialize($CrashInfo))
3234  ."' WHERE TaskId = ".intval($this->RunningTask["TaskId"]));
3235  }
3236  }
3237 
3238  print("\n");
3239  return;
3240  }
3241 
3258  private function AddToDirList($DirList, $Dir, $SearchLast, $SkipSlashCheck)
3259  {
3260  # convert incoming directory to array of directories (if needed)
3261  $Dirs = is_array($Dir) ? $Dir : array($Dir);
3262 
3263  # reverse array so directories are searched in specified order
3264  $Dirs = array_reverse($Dirs);
3265 
3266  # for each directory
3267  foreach ($Dirs as $Location)
3268  {
3269  # make sure directory includes trailing slash
3270  if (!$SkipSlashCheck)
3271  {
3272  $Location = $Location
3273  .((substr($Location, -1) != "/") ? "/" : "");
3274  }
3275 
3276  # remove directory from list if already present
3277  if (in_array($Location, $DirList))
3278  {
3279  $DirList = array_diff(
3280  $DirList, array($Location));
3281  }
3282 
3283  # add directory to list of directories
3284  if ($SearchLast)
3285  {
3286  array_push($DirList, $Location);
3287  }
3288  else
3289  {
3290  array_unshift($DirList, $Location);
3291  }
3292  }
3293 
3294  # return updated directory list to caller
3295  return $DirList;
3296  }
3297 
3305  private function ArrayPermutations($Items, $Perms = array())
3306  {
3307  if (empty($Items))
3308  {
3309  $Result = array($Perms);
3310  }
3311  else
3312  {
3313  $Result = array();
3314  for ($Index = count($Items) - 1; $Index >= 0; --$Index)
3315  {
3316  $NewItems = $Items;
3317  $NewPerms = $Perms;
3318  list($Segment) = array_splice($NewItems, $Index, 1);
3319  array_unshift($NewPerms, $Segment);
3320  $Result = array_merge($Result,
3321  $this->ArrayPermutations($NewItems, $NewPerms));
3322  }
3323  }
3324  return $Result;
3325  }
3326 
3333  private function OutputModificationCallbackShell($Matches)
3334  {
3335  # call previously-stored external function
3336  return call_user_func($this->OutputModificationCallbackInfo["Callback"],
3337  $Matches,
3338  $this->OutputModificationCallbackInfo["Pattern"],
3339  $this->OutputModificationCallbackInfo["Page"],
3340  $this->OutputModificationCallbackInfo["SearchPattern"]);
3341  }
3342 
3349  function UpdateSetting($FieldName, $NewValue = DB_NOVALUE)
3350  {
3351  return $this->DB->UpdateValue("ApplicationFrameworkSettings",
3352  $FieldName, $NewValue, NULL, $this->Settings);
3353  }
3354 
3356  private $InterfaceDirList = array(
3357  "local/interface/%ACTIVEUI%/",
3358  "interface/%ACTIVEUI%/",
3359  "local/interface/default/",
3360  "interface/default/",
3361  );
3366  private $IncludeDirList = array(
3367  "local/interface/%ACTIVEUI%/include/",
3368  "interface/%ACTIVEUI%/include/",
3369  "local/interface/default/include/",
3370  "interface/default/include/",
3371  );
3373  private $ImageDirList = array(
3374  "local/interface/%ACTIVEUI%/images/",
3375  "interface/%ACTIVEUI%/images/",
3376  "local/interface/default/images/",
3377  "interface/default/images/",
3378  );
3380  private $FunctionDirList = array(
3381  "local/interface/%ACTIVEUI%/include/",
3382  "interface/%ACTIVEUI%/include/",
3383  "local/interface/default/include/",
3384  "interface/default/include/",
3385  "local/include/",
3386  "include/",
3387  );
3388 
3389  const NOVALUE = ".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
3390 };
MaxTasks($NewValue=DB_NOVALUE)
Get/set maximum number of tasks to have running simultaneously.
const LOGLVL_ERROR
ERROR error logging level.
GetOrphanedTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently in queue.
SuppressHTMLOutput($NewSetting=TRUE)
Suppress loading of HTML files.
AddInterfaceDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for user interface (HTML/TPL) files.
AddIncludeDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for user interface include (CSS, JavaScript, common PHP, common HTML, etc) files.
static GetScriptUrl()
Retrieve SCRIPT_URL server value, pulling it from elsewhere if that variable isn&#39;t set...
const LOGLVL_INFO
INFO error logging level.
AddUnbufferedCallback($Callback, $Parameters=array())
Add a callback that will be executed after buffered content has been output and that won&#39;t have its o...
QueueUniqueTask($Callback, $Parameters=NULL, $Priority=self::PRIORITY_LOW, $Description="")
Add task to queue if not already in queue or currently running.
const LOGLVL_FATAL
FATAL error logging level.
UseMinimizedJavascript($NewSetting=NULL)
Get/set whether or not to check for and use minimized JavaScript files when getting a JavaScript UI f...
AddPostProcessingCall($FunctionName, &$Arg1=self::NOVALUE, &$Arg2=self::NOVALUE, &$Arg3=self::NOVALUE, &$Arg4=self::NOVALUE, &$Arg5=self::NOVALUE, &$Arg6=self::NOVALUE, &$Arg7=self::NOVALUE, &$Arg8=self::NOVALUE, &$Arg9=self::NOVALUE)
Add function to be called after HTML has been loaded.
GetCleanUrlForPath($Path)
Get the clean URL mapped for a path.
const PRIORITY_LOW
Lower priority.
Abstraction for forum messages and resource comments.
Definition: Message.php:15
GetQueuedTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently in queue.
LogFile($NewValue=NULL)
Get/set log file name.
GetCleanUrl()
Get the clean URL for the current page if one is available.
MaxExecutionTime($NewValue=NULL)
Get/set maximum PHP execution time.
RequireUIFile($FileName)
Add file to list of required UI files.
static FullUrl()
Get current full URL.
Top-level framework for web applications.
static BaseUrl()
Get current base URL (usually the part before index.php).
GetOrphanedTaskCount()
Retrieve current number of orphaned tasks.
SQL database abstraction object with smart query caching.
LogSlowPageLoads($NewValue=DB_NOVALUE)
Get/set whether logging of long page load times is enabled.
SlowPageLoadThreshold($NewValue=DB_NOVALUE)
Get/set how long a page load can take before it should be considered &quot;slow&quot; and may be logged...
GetTaskQueueSize($Priority=NULL)
Retrieve current number of tasks in queue.
const DB_NOVALUE
GetQueuedTaskCount($Callback=NULL, $Parameters=NULL, $Priority=NULL, $Description=NULL)
Get number of queued tasks that match supplied values.
DeleteTask($TaskId)
Remove task from task queues.
const LOGLVL_DEBUG
DEBUG error logging leve.
const EVENTTYPE_NAMED
Named result event type.
const EVENTTYPE_FIRST
First response event type.
static WasUrlRewritten($ScriptName="index.php")
Determine if the URL was rewritten, i.e., the script is being accessed through a URL that isn&#39;t direc...
const EVENTTYPE_DEFAULT
Default event type.
GetPageUrl()
Get the full URL to the page.
IsStaticOnlyEvent($EventName)
Report whether specified event only allows static callbacks.
IsRegisteredEvent($EventName)
Check if event has been registered (is available to be signaled).
static AddObjectDirectory($Dir, $Prefix="", $ClassPattern=NULL, $ClassReplacement=NULL)
Add directory to be searched for object files when autoloading.
SignalEvent($EventName, $Parameters=NULL)
Signal that an event has occured.
static BasePath()
Get current base path (usually the part after the host name).
const LOGLVL_TRACE
TRACE error logging level.
const LOGLVL_WARNING
WARNING error logging level.
const PRIORITY_MEDIUM
Medium (default) priority.
LogError($Level, $Msg)
Write error message to log.
OnCrash()
Called automatically at program termination to ensure output is written out.
GetKnownPeriodicEvents()
Get list of known periodic events.
const EVENTTYPE_CHAIN
Result chaining event type.
GetSecondsBeforeTimeout()
Get remaining available (PHP) execution time.
TaskIsInQueue($Callback, $Parameters=NULL)
Check if task is already in queue or currently running.
AddFunctionDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for function (&quot;F-&quot;) files.
LoggingLevel($NewValue=DB_NOVALUE)
Get/set logging level.
const ORDER_MIDDLE
Run hooked function after ORDER_FIRST and before ORDER_LAST events.
PHP
Definition: OAIClient.php:39
RegisterEvent($EventsOrEventName, $EventType=NULL)
Register one or more events that may be signaled.
SetJumpToPage($Page, $IsLiteral=FALSE)
Set URL of page to autoload after PHP page file is executed.
GetPageName()
Get name of page being loaded.
CleanUrlIsMapped($Path)
Report whether clean URL has already been mapped.
const PRIORITY_HIGH
Highest priority.
LoadFunction($Callback)
Attempt to load code for function or method if not currently available.
GetRunningTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently in queue.
FindCommonTemplate($BaseName)
Preserved for backward compatibility for use with code written prior to October 2012.
EventWillNextRunAt($EventName, $Callback)
Get date/time a periodic event will next run.
static SessionLifetime($NewValue=NULL)
Get/set session timeout in seconds.
HookEvent($EventsOrEventName, $Callback=NULL, $Order=self::ORDER_MIDDLE)
Hook one or more functions to be called when the specified event is signaled.
IsHookedEvent($EventName)
Check if an event is registered and is hooked to.
AddImageDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for image files.
HtmlCharset($NewSetting=NULL)
Get/set HTTP character encoding value.
const ORDER_FIRST
Run hooked function first (i.e.
HtaccessSupport()
Determine if .htaccess files are enabled.
GetUncleanUrlForPath($Path)
Get the unclean URL for mapped for a path.
TemplateLocationCacheExpirationInterval($NewInterval=DB_NOVALUE)
Get/set UI template location cache expiration period in minutes.
GetUncleanUrl()
Get the unclean URL for the current page.
JumpToPageIsSet()
Report whether a page to autoload has been set.
const ORDER_LAST
Run hooked function last (i.e.
UseBaseTag($NewValue=NULL)
Get/set whether or not to use the &quot;base&quot; tag to ensure relative URL paths are correct.
GetFreeMemory()
Get current amount of free memory.
UpdateSetting($FieldName, $NewValue=DB_NOVALUE)
Convenience function for getting/setting our settings.
GetTask($TaskId)
Retrieve task info from queue (either running or queued tasks).
TaskExecutionEnabled($NewValue=DB_NOVALUE)
Get/set whether automatic task execution is enabled.
LoadPage($PageName)
Load page PHP and HTML/TPL files.
AddEnvInclude($FileName)
Add file to be included to set up environment.
ReQueueOrphanedTask($TaskId, $NewPriority=NULL)
Move orphaned task back into queue.
GUIFile($FileName)
Search UI directories for specified image or CSS file and return name of correct file.
QueueTask($Callback, $Parameters=NULL, $Priority=self::PRIORITY_LOW, $Description="")
Add task to queue.
GetUserInterfaces()
Get the list of available user interfaces.
PUIFile($FileName)
Search UI directories for specified image or CSS file and print name of correct file.
ActiveUserInterface($UIName=NULL)
Get/set name of current active user interface.
GetPageLocation()
Get the URL path to the page without the base path, if present.
ObjectLocationCacheExpirationInterval($NewInterval=DB_NOVALUE)
Get/set object file location cache expiration period in minutes.
GetElapsedExecutionTime()
Get time elapsed since constructor was called.
SetBrowserDetectionFunc($DetectionFunc)
Specify function to use to detect the web browser type.
AddCleanUrl($Pattern, $Page, $GetVars=NULL, $Template=NULL)
Add clean URL mapping.
const PRIORITY_BACKGROUND
Lowest priority.
LogMessage($Level, $Msg)
Write status message to log.