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  }
98  function GlobalExceptionHandler($Exception)
99  {
100  # display exception info
101  $Location = $Exception->getFile()."[".$Exception->getLine()."]";
102  ?><table width="100%" cellpadding="5"
103  style="border: 2px solid #666666; background: #CCCCCC;
104  font-family: Courier New, Courier, monospace;
105  margin-top: 10px;"><tr><td>
106  <div style="color: #666666;">
107  <span style="font-size: 150%;">
108  <b>Uncaught Exception</b></span><br />
109  <b>Message:</b> <i><?PHP print $Exception->getMessage(); ?></i><br />
110  <b>Location:</b> <i><?PHP print $Location; ?></i><br />
111  <b>Trace:</b>
112  <blockquote><pre><?PHP print $Exception->getTraceAsString();
113  ?></pre></blockquote>
114  </div>
115  </td></tr></table><?PHP
116 
117  # log exception if possible
118  $LogMsg = "Uncaught exception (".$Exception->getMessage().").";
119  $this->LogError(self::LOGLVL_ERROR, $LogMsg);
120  }
134  static function AddObjectDirectory(
135  $Dir, $Prefix = "", $ClassPattern = NULL, $ClassReplacement = NULL)
136  {
137  # make sure directory has trailing slash
138  $Dir = $Dir.((substr($Dir, -1) != "/") ? "/" : "");
139 
140  # add directory to directory list
141  self::$ObjectDirectories = array_merge(
142  array($Dir => array(
143  "Prefix" => $Prefix,
144  "ClassPattern" => $ClassPattern,
145  "ClassReplacement" => $ClassReplacement,
146  )),
147  self::$ObjectDirectories);
148  }
149 
169  function AddImageDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
170  {
171  # add directories to existing image directory list
172  $this->ImageDirList = $this->AddToDirList(
173  $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
174  }
175 
196  function AddIncludeDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
197  {
198  # add directories to existing image directory list
199  $this->IncludeDirList = $this->AddToDirList(
200  $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
201  }
202 
222  function AddInterfaceDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
223  {
224  # add directories to existing image directory list
225  $this->InterfaceDirList = $this->AddToDirList(
226  $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
227  }
228 
248  function AddFunctionDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
249  {
250  # add directories to existing image directory list
251  $this->FunctionDirList = $this->AddToDirList(
252  $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
253  }
254 
260  function SetBrowserDetectionFunc($DetectionFunc)
261  {
262  $this->BrowserDetectFunc = $DetectionFunc;
263  }
264 
271  function AddUnbufferedCallback($Callback, $Parameters=array())
272  {
273  if (is_callable($Callback))
274  {
275  $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
276  }
277  }
278 
285  function TemplateLocationCacheExpirationInterval($NewInterval = -1)
286  {
287  if ($NewInterval >= 0)
288  {
289  $this->Settings["TemplateLocationCacheInterval"] = $NewInterval;
290  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
291  ." SET TemplateLocationCacheInterval = '"
292  .intval($NewInterval)."'");
293  }
294  return $this->Settings["TemplateLocationCacheInterval"];
295  }
296 
302  static function SessionLifetime($NewValue = NULL)
303  {
304  if ($NewValue !== NULL)
305  {
306  self::$SessionLifetime = $NewValue;
307  }
308  return self::$SessionLifetime;
309  }
310 
337  function AddCleanUrl($Pattern, $Page, $GetVars = NULL, $Template = NULL)
338  {
339  # save clean URL mapping parameters
340  $this->CleanUrlMappings[] = array(
341  "Pattern" => $Pattern,
342  "Page" => $Page,
343  "GetVars" => $GetVars,
344  );
345 
346  # if replacement template specified
347  if ($Template !== NULL)
348  {
349  # if GET parameters specified
350  if (count($GetVars))
351  {
352  # retrieve all possible permutations of GET parameters
353  $GetPerms = $this->ArrayPermutations(array_keys($GetVars));
354 
355  # for each permutation of GET parameters
356  foreach ($GetPerms as $VarPermutation)
357  {
358  # construct search pattern for permutation
359  $SearchPattern = "/href=([\"'])index\\.php\\?P=".$Page;
360  $GetVarSegment = "";
361  foreach ($VarPermutation as $GetVar)
362  {
363  if (preg_match("%\\\$[0-9]+%", $GetVars[$GetVar]))
364  {
365  $GetVarSegment .= "&amp;".$GetVar."=((?:(?!\\1)[^&])+)";
366  }
367  else
368  {
369  $GetVarSegment .= "&amp;".$GetVar."=".$GetVars[$GetVar];
370  }
371  }
372  $SearchPattern .= $GetVarSegment."\\1/i";
373 
374  # if template is actually a callback
375  if (is_callable($Template))
376  {
377  # add pattern to HTML output mod callbacks list
378  $this->OutputModificationCallbacks[] = array(
379  "Pattern" => $Pattern,
380  "Page" => $Page,
381  "SearchPattern" => $SearchPattern,
382  "Callback" => $Template,
383  );
384  }
385  else
386  {
387  # construct replacement string for permutation
388  $Replacement = $Template;
389  $Index = 2;
390  foreach ($VarPermutation as $GetVar)
391  {
392  $Replacement = str_replace(
393  "\$".$GetVar, "\$".$Index, $Replacement);
394  $Index++;
395  }
396  $Replacement = "href=\"".$Replacement."\"";
397 
398  # add pattern to HTML output modifications list
399  $this->OutputModificationPatterns[] = $SearchPattern;
400  $this->OutputModificationReplacements[] = $Replacement;
401  }
402  }
403  }
404  else
405  {
406  # construct search pattern
407  $SearchPattern = "/href=\"index\\.php\\?P=".$Page."\"/i";
408 
409  # if template is actually a callback
410  if (is_callable($Template))
411  {
412  # add pattern to HTML output mod callbacks list
413  $this->OutputModificationCallbacks[] = array(
414  "Pattern" => $Pattern,
415  "Page" => $Page,
416  "SearchPattern" => $SearchPattern,
417  "Callback" => $Template,
418  );
419  }
420  else
421  {
422  # add simple pattern to HTML output modifications list
423  $this->OutputModificationPatterns[] = $SearchPattern;
424  $this->OutputModificationReplacements[] = "href=\"".$Template."\"";
425  }
426  }
427  }
428  }
429 
435  function CleanUrlIsMapped($Path)
436  {
437  foreach ($this->CleanUrlMappings as $Info)
438  {
439  if (preg_match($Info["Pattern"], $Path))
440  {
441  return TRUE;
442  }
443  }
444  return FALSE;
445  }
446 
453  function GetCleanUrlForPath($Path)
454  {
455  # the search patterns and callbacks require a specific format
456  $Format = "href=\"".str_replace("&", "&amp;", $Path)."\"";
457  $Search = $Format;
458 
459  # perform any regular expression replacements on the search string
460  $Search = preg_replace(
461  $this->OutputModificationPatterns,
462  $this->OutputModificationReplacements,
463  $Search);
464 
465  # only run the callbacks if a replacement hasn't already been performed
466  if ($Search == $Format)
467  {
468  # perform any callback replacements on the search string
469  foreach ($this->OutputModificationCallbacks as $Info)
470  {
471  # make the information available to the callback
472  $this->OutputModificationCallbackInfo = $Info;
473 
474  # execute the callback
475  $Search = preg_replace_callback(
476  $Info["SearchPattern"],
477  array($this, "OutputModificationCallbackShell"),
478  $Search);
479  }
480  }
481 
482  # return the path untouched if no replacements were performed
483  if ($Search == $Format)
484  {
485  return $Path;
486  }
487 
488  # remove the bits added to the search string to get it recognized by
489  # the replacement expressions and callbacks
490  $Result = substr($Search, 6, -1);
491 
492  return $Result;
493  }
494 
501  public function GetUncleanUrlForPath($Path)
502  {
503  # for each clean URL mapping
504  foreach ($this->CleanUrlMappings as $Info)
505  {
506  # if current path matches the clean URL pattern
507  if (preg_match($Info["Pattern"], $Path, $Matches))
508  {
509  # the GET parameters for the URL, starting with the page name
510  $GetVars = array("P" => $Info["Page"]);
511 
512  # if additional $_GET variables specified for clean URL
513  if ($Info["GetVars"] !== NULL)
514  {
515  # for each $_GET variable specified for clean URL
516  foreach ($Info["GetVars"] as $VarName => $VarTemplate)
517  {
518  # start with template for variable value
519  $Value = $VarTemplate;
520 
521  # for each subpattern matched in current URL
522  foreach ($Matches as $Index => $Match)
523  {
524  # if not first (whole) match
525  if ($Index > 0)
526  {
527  # make any substitutions in template
528  $Value = str_replace("$".$Index, $Match, $Value);
529  }
530  }
531 
532  # add the GET variable
533  $GetVars[$VarName] = $Value;
534  }
535  }
536 
537  # return the unclean URL
538  return "index.php?" . http_build_query($GetVars);
539  }
540  }
541 
542  # return the path unchanged
543  return $Path;
544  }
545 
551  function GetCleanUrl()
552  {
553  return $this->GetCleanUrlForPath($this->GetUncleanUrl());
554  }
555 
560  function GetUncleanUrl()
561  {
562  $GetVars = array("P" => $this->GetPageName()) + $_GET;
563  return "index.php?" . http_build_query($GetVars);
564  }
565 
570  function LoadPage($PageName)
571  {
572  # perform any clean URL rewriting
573  $PageName = $this->RewriteCleanUrls($PageName);
574 
575  # sanitize incoming page name and save local copy
576  $PageName = preg_replace("/[^a-zA-Z0-9_.-]/", "", $PageName);
577  $this->PageName = $PageName;
578 
579  # buffer any output from includes or PHP file
580  ob_start();
581 
582  # include any files needed to set up execution environment
583  foreach ($this->EnvIncludes as $IncludeFile)
584  {
585  include($IncludeFile);
586  }
587 
588  # signal page load
589  $this->SignalEvent("EVENT_PAGE_LOAD", array("PageName" => $PageName));
590 
591  # signal PHP file load
592  $SignalResult = $this->SignalEvent("EVENT_PHP_FILE_LOAD", array(
593  "PageName" => $PageName));
594 
595  # if signal handler returned new page name value
596  $NewPageName = $PageName;
597  if (($SignalResult["PageName"] != $PageName)
598  && strlen($SignalResult["PageName"]))
599  {
600  # if new page name value is page file
601  if (file_exists($SignalResult["PageName"]))
602  {
603  # use new value for PHP file name
604  $PageFile = $SignalResult["PageName"];
605  }
606  else
607  {
608  # use new value for page name
609  $NewPageName = $SignalResult["PageName"];
610  }
611 
612  # update local copy of page name
613  $this->PageName = $NewPageName;
614  }
615 
616  # if we do not already have a PHP file
617  if (!isset($PageFile))
618  {
619  # look for PHP file for page
620  $OurPageFile = "pages/".$NewPageName.".php";
621  $LocalPageFile = "local/pages/".$NewPageName.".php";
622  $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
623  : (file_exists($OurPageFile) ? $OurPageFile
624  : "pages/".$this->DefaultPage.".php");
625  }
626 
627  # load PHP file
628  include($PageFile);
629 
630  # save buffered output to be displayed later after HTML file loads
631  $PageOutput = ob_get_contents();
632  ob_end_clean();
633 
634  # signal PHP file load is complete
635  ob_start();
636  $Context["Variables"] = get_defined_vars();
637  $this->SignalEvent("EVENT_PHP_FILE_LOAD_COMPLETE",
638  array("PageName" => $PageName, "Context" => $Context));
639  $PageCompleteOutput = ob_get_contents();
640  ob_end_clean();
641 
642  # set up for possible TSR (Terminate and Stay Resident :))
643  $ShouldTSR = $this->PrepForTSR();
644 
645  # if PHP file indicated we should autorefresh to somewhere else
646  if ($this->JumpToPage)
647  {
648  if (!strlen(trim($PageOutput)))
649  {
650  ?><html>
651  <head>
652  <meta http-equiv="refresh" content="0; URL=<?PHP
653  print($this->JumpToPage); ?>">
654  </head>
655  <body bgcolor="white">
656  </body>
657  </html><?PHP
658  }
659  }
660  # else if HTML loading is not suppressed
661  elseif (!$this->SuppressHTML)
662  {
663  # set content-type to get rid of diacritic errors
664  header("Content-Type: text/html; charset="
665  .$this->HtmlCharset, TRUE);
666 
667  # load common HTML file (defines common functions) if available
668  $CommonHtmlFile = $this->FindFile($this->IncludeDirList,
669  "Common", array("tpl", "html"));
670  if ($CommonHtmlFile) { include($CommonHtmlFile); }
671 
672  # load UI functions
673  $this->LoadUIFunctions();
674 
675  # begin buffering content
676  ob_start();
677 
678  # signal HTML file load
679  $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD", array(
680  "PageName" => $PageName));
681 
682  # if signal handler returned new page name value
683  $NewPageName = $PageName;
684  $PageContentFile = NULL;
685  if (($SignalResult["PageName"] != $PageName)
686  && strlen($SignalResult["PageName"]))
687  {
688  # if new page name value is HTML file
689  if (file_exists($SignalResult["PageName"]))
690  {
691  # use new value for HTML file name
692  $PageContentFile = $SignalResult["PageName"];
693  }
694  else
695  {
696  # use new value for page name
697  $NewPageName = $SignalResult["PageName"];
698  }
699  }
700 
701  # load page content HTML file if available
702  if ($PageContentFile === NULL)
703  {
704  $PageContentFile = $this->FindFile(
705  $this->InterfaceDirList, $NewPageName,
706  array("tpl", "html"));
707  }
708  if ($PageContentFile)
709  {
710  include($PageContentFile);
711  }
712  else
713  {
714  print "<h2>ERROR: No HTML/TPL template found"
715  ." for this page.</h2>";
716  }
717 
718  # signal HTML file load complete
719  $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD_COMPLETE");
720 
721  # stop buffering and save output
722  $PageContentOutput = ob_get_contents();
723  ob_end_clean();
724 
725  # load page start HTML file if available
726  ob_start();
727  $PageStartFile = $this->FindFile($this->IncludeDirList, "Start",
728  array("tpl", "html"), array("StdPage", "StandardPage"));
729  if ($PageStartFile) { include($PageStartFile); }
730  $PageStartOutput = ob_get_contents();
731  ob_end_clean();
732 
733  # load page end HTML file if available
734  ob_start();
735  $PageEndFile = $this->FindFile($this->IncludeDirList, "End",
736  array("tpl", "html"), array("StdPage", "StandardPage"));
737  if ($PageEndFile) { include($PageEndFile); }
738  $PageEndOutput = ob_get_contents();
739  ob_end_clean();
740 
741  # get list of any required files not loaded
742  $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
743 
744  # if a browser detection function has been made available
745  if (is_callable($this->BrowserDetectFunc))
746  {
747  # call function to get browser list
748  $Browsers = call_user_func($this->BrowserDetectFunc);
749 
750  # for each required file
751  $NewRequiredFiles = array();
752  foreach ($RequiredFiles as $File)
753  {
754  # if file name includes browser keyword
755  if (preg_match("/%BROWSER%/", $File))
756  {
757  # for each browser
758  foreach ($Browsers as $Browser)
759  {
760  # substitute in browser name and add to new file list
761  $NewRequiredFiles[] = preg_replace(
762  "/%BROWSER%/", $Browser, $File);
763  }
764  }
765  else
766  {
767  # add to new file list
768  $NewRequiredFiles[] = $File;
769  }
770  }
771  $RequiredFiles = $NewRequiredFiles;
772  }
773 
774  # for each required file
775  foreach ($RequiredFiles as $File)
776  {
777  # locate specific file to use
778  $FilePath = $this->GUIFile($File);
779 
780  # if file was found
781  if ($FilePath)
782  {
783  # determine file type
784  $NamePieces = explode(".", $File);
785  $FileSuffix = strtolower(array_pop($NamePieces));
786 
787  # add file to HTML output based on file type
788  $FilePath = htmlspecialchars($FilePath);
789  switch ($FileSuffix)
790  {
791  case "js":
792  $Tag = '<script type="text/javascript" src="'
793  .$FilePath.'"></script>';
794  $PageEndOutput = preg_replace(
795  "#</body>#i", $Tag."\n</body>", $PageEndOutput, 1);
796  break;
797 
798  case "css":
799  $Tag = '<link rel="stylesheet" type="text/css"'
800  .' media="all" href="'.$FilePath.'">';
801  $PageStartOutput = preg_replace(
802  "#</head>#i", $Tag."\n</head>", $PageStartOutput, 1);
803  break;
804  }
805  }
806  }
807 
808  # assemble full page
809  $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
810 
811  # perform any regular expression replacements in output
812  $FullPageOutput = preg_replace($this->OutputModificationPatterns,
813  $this->OutputModificationReplacements, $FullPageOutput);
814 
815  # perform any callback replacements in output
816  foreach ($this->OutputModificationCallbacks as $Info)
817  {
818  $this->OutputModificationCallbackInfo = $Info;
819  $FullPageOutput = preg_replace_callback($Info["SearchPattern"],
820  array($this, "OutputModificationCallbackShell"),
821  $FullPageOutput);
822  }
823 
824  # if relative paths may not work because we were invoked via clean URL
825  if ($this->CleanUrlRewritePerformed || self::WasUrlRewritten())
826  {
827  # if using the <base> tag is okay
828  $BaseUrl = $this->BaseUrl();
829  if ($this->UseBaseTag)
830  {
831  # add <base> tag to header
832  $PageStartOutput = preg_replace("%<head>%",
833  "<head><base href=\"".$BaseUrl."\" />",
834  $PageStartOutput);
835 
836  # re-assemble full page with new header
837  $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
838 
839  # the absolute URL to the current page
840  $FullUrl = $BaseUrl . $this->GetPageLocation();
841 
842  # make HREF attribute values with just a fragment ID
843  # absolute since they don't work with the <base> tag because
844  # they are relative to the current page/URL, not the site
845  # root
846  $FullPageOutput = preg_replace(
847  array("%href=\"(#[^:\" ]+)\"%i", "%href='(#[^:' ]+)'%i"),
848  array("href=\"".$FullUrl."$1\"", "href='".$FullUrl."$1'"),
849  $FullPageOutput);
850  }
851  else
852  {
853  # try to fix any relative paths throughout code
854  $FullPageOutput = preg_replace(array(
855  "%src=\"([^?*:;{}\\\\\" ]+)\.(js|css|gif|png|jpg)\"%i",
856  "%src='([^?*:;{}\\\\' ]+)\.(js|css|gif|png|jpg)'%i",
857  # don't rewrite HREF attributes that are just
858  # fragment IDs because they are relative to the
859  # current page/URL, not the site root
860  "%href=\"([^#][^:\" ]*)\"%i",
861  "%href='([^#][^:' ]*)'%i",
862  "%action=\"([^#][^:\" ]*)\"%i",
863  "%action='([^#][^:' ]*)'%i",
864  "%@import\s+url\(\"([^:\" ]+)\"\s*\)%i",
865  "%@import\s+url\('([^:\" ]+)'\s*\)%i",
866  "%@import\s+\"([^:\" ]+)\"\s*%i",
867  "%@import\s+'([^:\" ]+)'\s*%i",
868  ),
869  array(
870  "src=\"".$BaseUrl."$1.$2\"",
871  "src=\"".$BaseUrl."$1.$2\"",
872  "href=\"".$BaseUrl."$1\"",
873  "href=\"".$BaseUrl."$1\"",
874  "action=\"".$BaseUrl."$1\"",
875  "action=\"".$BaseUrl."$1\"",
876  "@import url(\"".$BaseUrl."$1\")",
877  "@import url('".$BaseUrl."$1')",
878  "@import \"".$BaseUrl."$1\"",
879  "@import '".$BaseUrl."$1'",
880  ),
881  $FullPageOutput);
882  }
883  }
884 
885  # provide the opportunity to modify full page output
886  $SignalResult = $this->SignalEvent("EVENT_PAGE_OUTPUT_FILTER", array(
887  "PageOutput" => $FullPageOutput));
888  if (isset($SignalResult["PageOutput"])
889  && strlen($SignalResult["PageOutput"]))
890  {
891  $FullPageOutput = $SignalResult["PageOutput"];
892  }
893 
894  # write out full page
895  print $FullPageOutput;
896  }
897 
898  # run any post-processing routines
899  foreach ($this->PostProcessingFuncs as $Func)
900  {
901  call_user_func_array($Func["FunctionName"], $Func["Arguments"]);
902  }
903 
904  # write out any output buffered from page code execution
905  if (strlen($PageOutput))
906  {
907  if (!$this->SuppressHTML)
908  {
909  ?><table width="100%" cellpadding="5"
910  style="border: 2px solid #666666; background: #CCCCCC;
911  font-family: Courier New, Courier, monospace;
912  margin-top: 10px;"><tr><td><?PHP
913  }
914  if ($this->JumpToPage)
915  {
916  ?><div style="color: #666666;"><span style="font-size: 150%;">
917  <b>Page Jump Aborted</b></span>
918  (because of error or other unexpected output)<br />
919  <b>Jump Target:</b>
920  <i><?PHP print($this->JumpToPage); ?></i></div><?PHP
921  }
922  print $PageOutput;
923  if (!$this->SuppressHTML)
924  {
925  ?></td></tr></table><?PHP
926  }
927  }
928 
929  # write out any output buffered from the page code execution complete signal
930  if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
931  {
932  print $PageCompleteOutput;
933  }
934 
935  # execute callbacks that should not have their output buffered
936  foreach ($this->UnbufferedCallbacks as $Callback)
937  {
938  call_user_func_array($Callback[0], $Callback[1]);
939  }
940 
941  # terminate and stay resident (TSR!) if indicated and HTML has been output
942  # (only TSR if HTML has been output because otherwise browsers will misbehave)
943  if ($ShouldTSR) { $this->LaunchTSR(); }
944  }
945 
951  function GetPageName()
952  {
953  return $this->PageName;
954  }
955 
961  function GetPageLocation()
962  {
963  # retrieve current URL
964  $Url = $this->GetScriptUrl();
965 
966  # remove the base path if present
967  $BasePath = $this->Settings["BasePath"];
968  if (stripos($Url, $BasePath) === 0)
969  {
970  $Url = substr($Url, strlen($BasePath));
971  }
972 
973  return $Url;
974  }
975 
980  function GetPageUrl()
981  {
982  return self::BaseUrl() . $this->GetPageLocation();
983  }
984 
993  function SetJumpToPage($Page, $IsLiteral = FALSE)
994  {
995  if (!is_null($Page)
996  && (!$IsLiteral)
997  && (strpos($Page, "?") === FALSE)
998  && ((strpos($Page, "=") !== FALSE)
999  || ((stripos($Page, ".php") === FALSE)
1000  && (stripos($Page, ".htm") === FALSE)
1001  && (strpos($Page, "/") === FALSE)))
1002  && (stripos($Page, "http://") !== 0)
1003  && (stripos($Page, "https://") !== 0))
1004  {
1005  $this->JumpToPage = self::BaseUrl() . "index.php?P=".$Page;
1006  }
1007  else
1008  {
1009  $this->JumpToPage = $Page;
1010  }
1011  }
1012 
1017  function JumpToPageIsSet()
1018  {
1019  return ($this->JumpToPage === NULL) ? FALSE : TRUE;
1020  }
1021 
1031  function HtmlCharset($NewSetting = NULL)
1032  {
1033  if ($NewSetting !== NULL) { $this->HtmlCharset = $NewSetting; }
1034  return $this->HtmlCharset;
1035  }
1036 
1043  public function UseMinimizedJavascript($NewSetting = NULL)
1044  {
1045  if ($NewSetting !== NULL) { $this->UseMinimizedJavascript = $NewSetting; }
1046  return $this->UseMinimizedJavascript;
1047  }
1048 
1059  function UseBaseTag($NewValue = NULL)
1060  {
1061  if ($NewValue !== NULL) { $this->UseBaseTag = $NewValue ? TRUE : FALSE; }
1062  return $this->UseBaseTag;
1063  }
1064 
1071  function SuppressHTMLOutput($NewSetting = TRUE)
1072  {
1073  $this->SuppressHTML = $NewSetting;
1074  }
1075 
1082  function ActiveUserInterface($UIName = NULL)
1083  {
1084  if ($UIName !== NULL)
1085  {
1086  $this->ActiveUI = preg_replace("/^SPTUI--/", "", $UIName);
1087  }
1088  return $this->ActiveUI;
1089  }
1090 
1097  {
1098  # possible UI directories
1099  $InterfaceDirs = array(
1100  "interface",
1101  "local/interface");
1102 
1103  # start out with an empty list
1104  $Interfaces = array();
1105 
1106  # for each possible UI directory
1107  foreach ($InterfaceDirs as $InterfaceDir)
1108  {
1109  $Dir = dir($InterfaceDir);
1110 
1111  # for each file in current directory
1112  while (($DirEntry = $Dir->read()) !== FALSE)
1113  {
1114  $InterfacePath = $InterfaceDir."/".$DirEntry;
1115 
1116  # skip anything that doesn't have a name in the required format
1117  if (!preg_match('/^[a-zA-Z]+$/', $DirEntry))
1118  {
1119  continue;
1120  }
1121 
1122  # skip anything that isn't a directory
1123  if (!is_dir($InterfacePath))
1124  {
1125  continue;
1126  }
1127 
1128  # read the UI name (if available)
1129  $UIName = @file_get_contents($InterfacePath."/NAME");
1130 
1131  # use the directory name if the UI name isn't available
1132  if ($UIName === FALSE || !strlen($UIName))
1133  {
1134  $UIName = $DirEntry;
1135  }
1136 
1137  $Interfaces[$InterfacePath] = $UIName;
1138  }
1139 
1140  $Dir->close();
1141  }
1142 
1143  # return list to caller
1144  return $Interfaces;
1145  }
1146 
1162  function AddPostProcessingCall($FunctionName,
1163  &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
1164  &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
1165  &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
1166  {
1167  $FuncIndex = count($this->PostProcessingFuncs);
1168  $this->PostProcessingFuncs[$FuncIndex]["FunctionName"] = $FunctionName;
1169  $this->PostProcessingFuncs[$FuncIndex]["Arguments"] = array();
1170  $Index = 1;
1171  while (isset(${"Arg".$Index}) && (${"Arg".$Index} !== self::NOVALUE))
1172  {
1173  $this->PostProcessingFuncs[$FuncIndex]["Arguments"][$Index]
1174  =& ${"Arg".$Index};
1175  $Index++;
1176  }
1177  }
1178 
1184  function AddEnvInclude($FileName)
1185  {
1186  $this->EnvIncludes[] = $FileName;
1187  }
1188 
1195  function GUIFile($FileName)
1196  {
1197  # determine if the file is an image or JavaScript file
1198  $FileIsImage = preg_match("/\.(gif|jpg|png)$/", $FileName);
1199  $FileIsJavascript = preg_match("/\.js$/", $FileName);
1200 
1201  # determine which location to search based on file suffix
1202  $DirList = $FileIsImage ? $this->ImageDirList : $this->IncludeDirList;
1203 
1204  # if directed to get a minimized JavaScript file
1205  if ($FileIsJavascript && $this->UseMinimizedJavascript)
1206  {
1207  # first try to find the minimized JavaScript file
1208  $MinimizedFileName = substr_replace($FileName, ".min", -3, 0);
1209  $FoundFileName = $this->FindFile($DirList, $MinimizedFileName);
1210 
1211  # search for the regular file if a minimized file wasn't found
1212  if (is_null($FoundFileName))
1213  {
1214  $FoundFileName = $this->FindFile($DirList, $FileName);
1215  }
1216  }
1217 
1218  # otherwise just search for the file
1219  else
1220  {
1221  $FoundFileName = $this->FindFile($DirList, $FileName);
1222  }
1223 
1224  # add non-image files to list of found files (used for required files loading)
1225  if (!$FileIsImage) { $this->FoundUIFiles[] = basename($FoundFileName); }
1226 
1227  # return file name to caller
1228  return $FoundFileName;
1229  }
1230 
1240  function PUIFile($FileName)
1241  {
1242  $FullFileName = $this->GUIFile($FileName);
1243  if ($FullFileName) { print($FullFileName); }
1244  }
1245 
1253  function RequireUIFile($FileName)
1254  {
1255  $this->AdditionalRequiredUIFiles[] = $FileName;
1256  }
1257 
1266  function LoadFunction($Callback)
1267  {
1268  # if specified function is not currently available
1269  if (!is_callable($Callback))
1270  {
1271  # if function info looks legal
1272  if (is_string($Callback) && strlen($Callback))
1273  {
1274  # start with function directory list
1275  $Locations = $this->FunctionDirList;
1276 
1277  # add object directories to list
1278  $Locations = array_merge(
1279  $Locations, array_keys(self::$ObjectDirectories));
1280 
1281  # look for function file
1282  $FunctionFileName = $this->FindFile($Locations, "F-".$Callback,
1283  array("php", "html"));
1284 
1285  # if function file was found
1286  if ($FunctionFileName)
1287  {
1288  # load function file
1289  include_once($FunctionFileName);
1290  }
1291  else
1292  {
1293  # log error indicating function load failed
1294  $this->LogError(self::LOGLVL_ERROR, "Unable to load function"
1295  ." for callback \"".$Callback."\".");
1296  }
1297  }
1298  else
1299  {
1300  # log error indicating specified function info was bad
1301  $this->LogError(self::LOGLVL_ERROR, "Unloadable callback value"
1302  ." (".$Callback.")"
1303  ." passed to AF::LoadFunction().");
1304  }
1305  }
1306 
1307  # report to caller whether function load succeeded
1308  return is_callable($Callback);
1309  }
1310 
1316  {
1317  return microtime(TRUE) - $this->ExecutionStartTime;
1318  }
1319 
1325  {
1326  return ini_get("max_execution_time") - $this->GetElapsedExecutionTime();
1327  }
1328 
1334  function GetFreeMemory()
1335  {
1336  $Str = strtoupper(ini_get("memory_limit"));
1337  if (substr($Str, -1) == "B") { $Str = substr($Str, 0, strlen($Str) - 1); }
1338  switch (substr($Str, -1))
1339  {
1340  case "K": $MemoryLimit = (int)$Str * 1024; break;
1341  case "M": $MemoryLimit = (int)$Str * 1048576; break;
1342  case "G": $MemoryLimit = (int)$Str * 1073741824; break;
1343  default: $MemoryLimit = (int)$Str; break;
1344  }
1345 
1346  return $MemoryLimit - memory_get_usage();
1347  }
1348 
1353  function HtaccessSupport()
1354  {
1355  # HTACCESS_SUPPORT is set in the .htaccess file
1356  return isset($_SERVER["HTACCESS_SUPPORT"]);
1357  }
1358 
1363  static function BaseUrl()
1364  {
1365  $Protocol = (isset($_SERVER["HTTPS"]) ? "https" : "http");
1366  $BaseUrl = $Protocol."://"
1367  .(($_SERVER["SERVER_NAME"] != "127.0.0.1")
1368  ? $_SERVER["SERVER_NAME"]
1369  : $_SERVER["HTTP_HOST"])
1370  .dirname($_SERVER["SCRIPT_NAME"]);
1371  if (substr($BaseUrl, -1) != "/") { $BaseUrl .= "/"; }
1372  return $BaseUrl;
1373  }
1374 
1379  static function BasePath()
1380  {
1381  $BasePath = dirname($_SERVER["SCRIPT_NAME"]);
1382 
1383  if (substr($BasePath, -1) != "/")
1384  {
1385  $BasePath .= "/";
1386  }
1387 
1388  return $BasePath;
1389  }
1390 
1396  static function GetScriptUrl()
1397  {
1398  if (array_key_exists("SCRIPT_URL", $_SERVER))
1399  {
1400  return $_SERVER["SCRIPT_URL"];
1401  }
1402  elseif (array_key_exists("REDIRECT_URL", $_SERVER))
1403  {
1404  return $_SERVER["REDIRECT_URL"];
1405  }
1406  elseif (array_key_exists("REQUEST_URI", $_SERVER))
1407  {
1408  $Pieces = parse_url($_SERVER["REQUEST_URI"]);
1409  return $Pieces["path"];
1410  }
1411  else
1412  {
1413  return NULL;
1414  }
1415  }
1416 
1425  static function WasUrlRewritten($ScriptName="index.php")
1426  {
1427  # needed to get the path of the URL minus the query and fragment pieces
1428  $Components = parse_url(self::GetScriptUrl());
1429 
1430  # if parsing was successful and a path is set
1431  if (is_array($Components) && isset($Components["path"]))
1432  {
1433  $BasePath = self::BasePath();
1434  $Path = $Components["path"];
1435 
1436  # the URL was rewritten if the path isn't the base path, i.e., the
1437  # home page, and the file in the URL isn't the script generating the
1438  # page
1439  if ($BasePath != $Path && basename($Path) != $ScriptName)
1440  {
1441  return TRUE;
1442  }
1443  }
1444 
1445  # the URL wasn't rewritten
1446  return FALSE;
1447  }
1448 
1462  function LogError($Level, $Msg)
1463  {
1464  # if error level is at or below current logging level
1465  if ($this->Settings["LoggingLevel"] >= $Level)
1466  {
1467  # attempt to log error message
1468  $Result = $this->LogMessage($Level, $Msg);
1469 
1470  # if logging attempt failed and level indicated significant error
1471  if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
1472  {
1473  # throw exception about inability to log error
1474  static $AlreadyThrewException = FALSE;
1475  if (!$AlreadyThrewException)
1476  {
1477  $AlreadyThrewException = TRUE;
1478  throw new Exception("Unable to log error (".$Level.": ".$Msg.").");
1479  }
1480  }
1481 
1482  # report to caller whether message was logged
1483  return $Result;
1484  }
1485  else
1486  {
1487  # report to caller that message was not logged
1488  return FALSE;
1489  }
1490  }
1491 
1503  function LogMessage($Level, $Msg)
1504  {
1505  # if message level is at or below current logging level
1506  if ($this->Settings["LoggingLevel"] >= $Level)
1507  {
1508  # attempt to open log file
1509  $FHndl = @fopen($this->LogFileName, "a");
1510 
1511  # if log file could not be open
1512  if ($FHndl === FALSE)
1513  {
1514  # report to caller that message was not logged
1515  return FALSE;
1516  }
1517  else
1518  {
1519  # format log entry
1520  $ErrorAbbrevs = array(
1521  self::LOGLVL_FATAL => "FTL",
1522  self::LOGLVL_ERROR => "ERR",
1523  self::LOGLVL_WARNING => "WRN",
1524  self::LOGLVL_INFO => "INF",
1525  self::LOGLVL_DEBUG => "DBG",
1526  self::LOGLVL_TRACE => "TRC",
1527  );
1528  $LogEntry = date("Y-m-d H:i:s")
1529  ." ".($this->RunningInBackground ? "B" : "F")
1530  ." ".$ErrorAbbrevs[$Level]
1531  ." ".$Msg;
1532 
1533  # write entry to log
1534  $Success = fputs($FHndl, $LogEntry."\n");
1535 
1536  # close log file
1537  fclose($FHndl);
1538 
1539  # report to caller whether message was logged
1540  return ($Success === FALSE) ? FALSE : TRUE;
1541  }
1542  }
1543  else
1544  {
1545  # report to caller that message was not logged
1546  return FALSE;
1547  }
1548  }
1549 
1571  function LoggingLevel($NewValue = NULL)
1572  {
1573  # if new logging level was specified
1574  if ($NewValue !== NULL)
1575  {
1576  # constrain new level to within legal bounds and store locally
1577  $this->Settings["LoggingLevel"] = max(min($NewValue, 6), 1);
1578 
1579  # save new logging level in database
1580  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
1581  ." SET LoggingLevel = "
1582  .intval($this->Settings["LoggingLevel"]));
1583  }
1584 
1585  # report current logging level to caller
1586  return $this->Settings["LoggingLevel"];
1587  }
1588 
1595  function LogFile($NewValue = NULL)
1596  {
1597  if ($NewValue !== NULL) { $this->LogFileName = $NewValue; }
1598  return $this->LogFileName;
1599  }
1600 
1605  const LOGLVL_TRACE = 6;
1610  const LOGLVL_DEBUG = 5;
1616  const LOGLVL_INFO = 4;
1621  const LOGLVL_WARNING = 3;
1627  const LOGLVL_ERROR = 2;
1632  const LOGLVL_FATAL = 1;
1633 
1634  /*@)*/ /* Application Framework */
1635 
1636  # ---- Event Handling ----------------------------------------------------
1637  /*@(*/
1639 
1649  const EVENTTYPE_CHAIN = 2;
1655  const EVENTTYPE_FIRST = 3;
1663  const EVENTTYPE_NAMED = 4;
1664 
1666  const ORDER_FIRST = 1;
1668  const ORDER_MIDDLE = 2;
1670  const ORDER_LAST = 3;
1671 
1680  function RegisterEvent($EventsOrEventName, $EventType = NULL)
1681  {
1682  # convert parameters to array if not already in that form
1683  $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1684  : array($EventsOrEventName => $Type);
1685 
1686  # for each event
1687  foreach ($Events as $Name => $Type)
1688  {
1689  # store event information
1690  $this->RegisteredEvents[$Name]["Type"] = $Type;
1691  $this->RegisteredEvents[$Name]["Hooks"] = array();
1692  }
1693  }
1694 
1701  function IsRegisteredEvent($EventName)
1702  {
1703  return array_key_exists($EventName, $this->RegisteredEvents)
1704  ? TRUE : FALSE;
1705  }
1706 
1713  function IsHookedEvent($EventName)
1714  {
1715  # the event isn't hooked to if it isn't even registered
1716  if (!$this->IsRegisteredEvent($EventName))
1717  {
1718  return FALSE;
1719  }
1720 
1721  # return TRUE if there is at least one callback hooked to the event
1722  return count($this->RegisteredEvents[$EventName]["Hooks"]) > 0;
1723  }
1724 
1738  function HookEvent($EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
1739  {
1740  # convert parameters to array if not already in that form
1741  $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1742  : array($EventsOrEventName => $Callback);
1743 
1744  # for each event
1745  $Success = TRUE;
1746  foreach ($Events as $EventName => $EventCallback)
1747  {
1748  # if callback is valid
1749  if (is_callable($EventCallback))
1750  {
1751  # if this is a periodic event we process internally
1752  if (isset($this->PeriodicEvents[$EventName]))
1753  {
1754  # process event now
1755  $this->ProcessPeriodicEvent($EventName, $EventCallback);
1756  }
1757  # if specified event has been registered
1758  elseif (isset($this->RegisteredEvents[$EventName]))
1759  {
1760  # add callback for event
1761  $this->RegisteredEvents[$EventName]["Hooks"][]
1762  = array("Callback" => $EventCallback, "Order" => $Order);
1763 
1764  # sort callbacks by order
1765  if (count($this->RegisteredEvents[$EventName]["Hooks"]) > 1)
1766  {
1767  usort($this->RegisteredEvents[$EventName]["Hooks"],
1768  array("ApplicationFramework", "HookEvent_OrderCompare"));
1769  }
1770  }
1771  else
1772  {
1773  $Success = FALSE;
1774  }
1775  }
1776  else
1777  {
1778  $Success = FALSE;
1779  }
1780  }
1781 
1782  # report to caller whether all callbacks were hooked
1783  return $Success;
1784  }
1786  private static function HookEvent_OrderCompare($A, $B)
1787  {
1788  if ($A["Order"] == $B["Order"]) { return 0; }
1789  return ($A["Order"] < $B["Order"]) ? -1 : 1;
1790  }
1791 
1800  function SignalEvent($EventName, $Parameters = NULL)
1801  {
1802  $ReturnValue = NULL;
1803 
1804  # if event has been registered
1805  if (isset($this->RegisteredEvents[$EventName]))
1806  {
1807  # set up default return value (if not NULL)
1808  switch ($this->RegisteredEvents[$EventName]["Type"])
1809  {
1810  case self::EVENTTYPE_CHAIN:
1811  $ReturnValue = $Parameters;
1812  break;
1813 
1814  case self::EVENTTYPE_NAMED:
1815  $ReturnValue = array();
1816  break;
1817  }
1818 
1819  # for each callback for this event
1820  foreach ($this->RegisteredEvents[$EventName]["Hooks"] as $Hook)
1821  {
1822  # invoke callback
1823  $Callback = $Hook["Callback"];
1824  $Result = ($Parameters !== NULL)
1825  ? call_user_func_array($Callback, $Parameters)
1826  : call_user_func($Callback);
1827 
1828  # process return value based on event type
1829  switch ($this->RegisteredEvents[$EventName]["Type"])
1830  {
1831  case self::EVENTTYPE_CHAIN:
1832  if ($Result !== NULL)
1833  {
1834  foreach ($Parameters as $Index => $Value)
1835  {
1836  if (array_key_exists($Index, $Result))
1837  {
1838  $Parameters[$Index] = $Result[$Index];
1839  }
1840  }
1841  $ReturnValue = $Parameters;
1842  }
1843  break;
1844 
1845  case self::EVENTTYPE_FIRST:
1846  if ($Result !== NULL)
1847  {
1848  $ReturnValue = $Result;
1849  break 2;
1850  }
1851  break;
1852 
1853  case self::EVENTTYPE_NAMED:
1854  $CallbackName = is_array($Callback)
1855  ? (is_object($Callback[0])
1856  ? get_class($Callback[0])
1857  : $Callback[0])."::".$Callback[1]
1858  : $Callback;
1859  $ReturnValue[$CallbackName] = $Result;
1860  break;
1861 
1862  default:
1863  break;
1864  }
1865  }
1866  }
1867  else
1868  {
1869  $this->LogError(self::LOGLVL_WARNING,
1870  "Unregistered event signaled (".$EventName.").");
1871  }
1872 
1873  # return value if any to caller
1874  return $ReturnValue;
1875  }
1876 
1882  function IsStaticOnlyEvent($EventName)
1883  {
1884  return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
1885  }
1886 
1897  function EventWillNextRunAt($EventName, $Callback)
1898  {
1899  # if event is not a periodic event report failure to caller
1900  if (!array_key_exists($EventName, $this->EventPeriods)) { return FALSE; }
1901 
1902  # retrieve last execution time for event if available
1903  $Signature = self::GetCallbackSignature($Callback);
1904  $LastRunTime = $this->DB->Query("SELECT LastRunAt FROM PeriodicEvents"
1905  ." WHERE Signature = '".addslashes($Signature)."'", "LastRunAt");
1906 
1907  # if event was not found report failure to caller
1908  if ($LastRunTime === NULL) { return FALSE; }
1909 
1910  # calculate next run time based on event period
1911  $NextRunTime = strtotime($LastRunTime) + $this->EventPeriods[$EventName];
1912 
1913  # report next run time to caller
1914  return $NextRunTime;
1915  }
1916 
1933  {
1934  # retrieve last execution times
1935  $this->DB->Query("SELECT * FROM PeriodicEvents");
1936  $LastRunTimes = $this->DB->FetchColumn("LastRunAt", "Signature");
1937 
1938  # for each known event
1939  $Events = array();
1940  foreach ($this->KnownPeriodicEvents as $Signature => $Info)
1941  {
1942  # if last run time for event is available
1943  if (array_key_exists($Signature, $LastRunTimes))
1944  {
1945  # calculate next run time for event
1946  $LastRun = strtotime($LastRunTimes[$Signature]);
1947  $NextRun = $LastRun + $this->EventPeriods[$Info["Period"]];
1948  if ($Info["Period"] == "EVENT_PERIODIC") { $LastRun = FALSE; }
1949  }
1950  else
1951  {
1952  # set info to indicate run times are not known
1953  $LastRun = FALSE;
1954  $NextRun = FALSE;
1955  }
1956 
1957  # add event info to list
1958  $Events[$Signature] = $Info;
1959  $Events[$Signature]["LastRun"] = $LastRun;
1960  $Events[$Signature]["NextRun"] = $NextRun;
1961  $Events[$Signature]["Parameters"] = NULL;
1962  }
1963 
1964  # return list of known events to caller
1965  return $Events;
1966  }
1967 
1968  /*@)*/ /* Event Handling */
1969 
1970  # ---- Task Management ---------------------------------------------------
1971  /*@(*/
1973 
1975  const PRIORITY_HIGH = 1;
1977  const PRIORITY_MEDIUM = 2;
1979  const PRIORITY_LOW = 3;
1982 
1995  function QueueTask($Callback, $Parameters = NULL,
1996  $Priority = self::PRIORITY_LOW, $Description = "")
1997  {
1998  # pack task info and write to database
1999  if ($Parameters === NULL) { $Parameters = array(); }
2000  $this->DB->Query("INSERT INTO TaskQueue"
2001  ." (Callback, Parameters, Priority, Description)"
2002  ." VALUES ('".addslashes(serialize($Callback))."', '"
2003  .addslashes(serialize($Parameters))."', ".intval($Priority).", '"
2004  .addslashes($Description)."')");
2005  }
2006 
2024  function QueueUniqueTask($Callback, $Parameters = NULL,
2025  $Priority = self::PRIORITY_LOW, $Description = "")
2026  {
2027  if ($this->TaskIsInQueue($Callback, $Parameters))
2028  {
2029  $QueryResult = $this->DB->Query("SELECT TaskId,Priority FROM TaskQueue"
2030  ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2031  .($Parameters ? " AND Parameters = '"
2032  .addslashes(serialize($Parameters))."'" : ""));
2033  if ($QueryResult !== FALSE)
2034  {
2035  $Record = $this->DB->FetchRow();
2036  if ($Record["Priority"] > $Priority)
2037  {
2038  $this->DB->Query("UPDATE TaskQueue"
2039  ." SET Priority = ".intval($Priority)
2040  ." WHERE TaskId = ".intval($Record["TaskId"]));
2041  }
2042  }
2043  return FALSE;
2044  }
2045  else
2046  {
2047  $this->QueueTask($Callback, $Parameters, $Priority, $Description);
2048  return TRUE;
2049  }
2050  }
2051 
2061  function TaskIsInQueue($Callback, $Parameters = NULL)
2062  {
2063  $QueuedCount = $this->DB->Query(
2064  "SELECT COUNT(*) AS FoundCount FROM TaskQueue"
2065  ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2066  .($Parameters ? " AND Parameters = '"
2067  .addslashes(serialize($Parameters))."'" : ""),
2068  "FoundCount");
2069  $RunningCount = $this->DB->Query(
2070  "SELECT COUNT(*) AS FoundCount FROM RunningTasks"
2071  ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2072  .($Parameters ? " AND Parameters = '"
2073  .addslashes(serialize($Parameters))."'" : ""),
2074  "FoundCount");
2075  $FoundCount = $QueuedCount + $RunningCount;
2076  return ($FoundCount ? TRUE : FALSE);
2077  }
2078 
2084  function GetTaskQueueSize($Priority = NULL)
2085  {
2086  return $this->GetQueuedTaskCount(NULL, NULL, $Priority);
2087  }
2088 
2096  function GetQueuedTaskList($Count = 100, $Offset = 0)
2097  {
2098  return $this->GetTaskList("SELECT * FROM TaskQueue"
2099  ." ORDER BY Priority, TaskId ", $Count, $Offset);
2100  }
2101 
2115  function GetQueuedTaskCount($Callback = NULL,
2116  $Parameters = NULL, $Priority = NULL, $Description = NULL)
2117  {
2118  $Query = "SELECT COUNT(*) AS TaskCount FROM TaskQueue";
2119  $Sep = " WHERE";
2120  if ($Callback !== NULL)
2121  {
2122  $Query .= $Sep." Callback = '".addslashes(serialize($Callback))."'";
2123  $Sep = " AND";
2124  }
2125  if ($Parameters !== NULL)
2126  {
2127  $Query .= $Sep." Parameters = '".addslashes(serialize($Parameters))."'";
2128  $Sep = " AND";
2129  }
2130  if ($Priority !== NULL)
2131  {
2132  $Query .= $Sep." Priority = ".intval($Priority);
2133  $Sep = " AND";
2134  }
2135  if ($Description !== NULL)
2136  {
2137  $Query .= $Sep." Description = '".addslashes($Description)."'";
2138  }
2139  return $this->DB->Query($Query, "TaskCount");
2140  }
2141 
2149  function GetRunningTaskList($Count = 100, $Offset = 0)
2150  {
2151  return $this->GetTaskList("SELECT * FROM RunningTasks"
2152  ." WHERE StartedAt >= '".date("Y-m-d H:i:s",
2153  (time() - ini_get("max_execution_time")))."'"
2154  ." ORDER BY StartedAt", $Count, $Offset);
2155  }
2156 
2164  function GetOrphanedTaskList($Count = 100, $Offset = 0)
2165  {
2166  return $this->GetTaskList("SELECT * FROM RunningTasks"
2167  ." WHERE StartedAt < '".date("Y-m-d H:i:s",
2168  (time() - ini_get("max_execution_time")))."'"
2169  ." ORDER BY StartedAt", $Count, $Offset);
2170  }
2171 
2177  {
2178  return $this->DB->Query("SELECT COUNT(*) AS Count FROM RunningTasks"
2179  ." WHERE StartedAt < '".date("Y-m-d H:i:s",
2180  (time() - ini_get("max_execution_time")))."'",
2181  "Count");
2182  }
2183 
2189  function ReQueueOrphanedTask($TaskId, $NewPriority = NULL)
2190  {
2191  $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
2192  $this->DB->Query("INSERT INTO TaskQueue"
2193  ." (Callback,Parameters,Priority,Description) "
2194  ."SELECT Callback, Parameters, Priority, Description"
2195  ." FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2196  if ($NewPriority !== NULL)
2197  {
2198  $NewTaskId = $this->DB->LastInsertId("TaskQueue");
2199  $this->DB->Query("UPDATE TaskQueue SET Priority = "
2200  .intval($NewPriority)
2201  ." WHERE TaskId = ".intval($NewTaskId));
2202  }
2203  $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2204  $this->DB->Query("UNLOCK TABLES");
2205  }
2206 
2211  function DeleteTask($TaskId)
2212  {
2213  $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2214  $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2215  }
2216 
2224  function GetTask($TaskId)
2225  {
2226  # assume task will not be found
2227  $Task = NULL;
2228 
2229  # look for task in task queue
2230  $this->DB->Query("SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2231 
2232  # if task was not found in queue
2233  if (!$this->DB->NumRowsSelected())
2234  {
2235  # look for task in running task list
2236  $this->DB->Query("SELECT * FROM RunningTasks WHERE TaskId = "
2237  .intval($TaskId));
2238  }
2239 
2240  # if task was found
2241  if ($this->DB->NumRowsSelected())
2242  {
2243  # if task was periodic
2244  $Row = $this->DB->FetchRow();
2245  if ($Row["Callback"] ==
2246  serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
2247  {
2248  # unpack periodic task callback
2249  $WrappedCallback = unserialize($Row["Parameters"]);
2250  $Task["Callback"] = $WrappedCallback[1];
2251  $Task["Parameters"] = $WrappedCallback[2];
2252  }
2253  else
2254  {
2255  # unpack task callback and parameters
2256  $Task["Callback"] = unserialize($Row["Callback"]);
2257  $Task["Parameters"] = unserialize($Row["Parameters"]);
2258  }
2259  }
2260 
2261  # return task to caller
2262  return $Task;
2263  }
2264 
2272  function TaskExecutionEnabled($NewValue = NULL)
2273  {
2274  if ($NewValue !== NULL)
2275  {
2276  $this->Settings["TaskExecutionEnabled"] = $NewValue ? 1 : 0;
2277  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
2278  ." SET TaskExecutionEnabled = "
2279  .$this->Settings["TaskExecutionEnabled"]);
2280  }
2281  return $this->Settings["TaskExecutionEnabled"];
2282  }
2283 
2289  function MaxTasks($NewValue = NULL)
2290  {
2291  if (func_num_args() && ($NewValue >= 1))
2292  {
2293  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
2294  ." SET MaxTasksRunning = ".intval($NewValue));
2295  $this->Settings["MaxTasksRunning"] = intval($NewValue);
2296  }
2297  return $this->Settings["MaxTasksRunning"];
2298  }
2299 
2307  function MaxExecutionTime($NewValue = NULL)
2308  {
2309  if (func_num_args() && !ini_get("safe_mode"))
2310  {
2311  if ($NewValue != $this->Settings["MaxExecTime"])
2312  {
2313  $this->Settings["MaxExecTime"] = max($NewValue, 5);
2314  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
2315  ." SET MaxExecTime = '"
2316  .intval($this->Settings["MaxExecTime"])."'");
2317  }
2318  ini_set("max_execution_time", $this->Settings["MaxExecTime"]);
2319  set_time_limit($this->Settings["MaxExecTime"]);
2320  }
2321  return ini_get("max_execution_time");
2322  }
2323 
2324  /*@)*/ /* Task Management */
2325 
2326  # ---- Backward Compatibility --------------------------------------------
2327  /*@(*/
2329 
2334  function FindCommonTemplate($BaseName)
2335  {
2336  return $this->FindFile(
2337  $this->IncludeDirList, $BaseName, array("tpl", "html"));
2338  }
2339 
2340  /*@)*/ /* Backward Compatibility */
2341 
2342 
2343  # ---- PRIVATE INTERFACE -------------------------------------------------
2344 
2345  private $ActiveUI = "default";
2346  private $BrowserDetectFunc;
2347  private $DB;
2348  private $DefaultPage = "Home";
2349  private $EnvIncludes = array();
2350  private $ExecutionStartTime;
2351  private $FoundUIFiles = array();
2352  private $AdditionalRequiredUIFiles = array();
2353  private $HtmlCharset = "UTF-8";
2354  private $UseMinimizedJavascript = FALSE;
2355  private $JumpToPage = NULL;
2356  private $PageName;
2357  private $LogFileName = "local/logs/site.log";
2358  private $MaxRunningTasksToTrack = 250;
2359  private $PostProcessingFuncs = array();
2360  private $RunningInBackground = FALSE;
2361  private $RunningTask;
2362  private $Settings;
2363  private $SuppressHTML = FALSE;
2364  private $SaveTemplateLocationCache = FALSE;
2365  private $UnbufferedCallbacks = array();
2366  private $UseBaseTag = FALSE;
2367  private $CleanUrlMappings = array();
2368  private $CleanUrlRewritePerformed = FALSE;
2369  private $OutputModificationPatterns = array();
2370  private $OutputModificationReplacements = array();
2371  private $OutputModificationCallbacks = array();
2372  private $OutputModificationCallbackInfo;
2373 
2374  private static $AppName = "ScoutAF";
2375  private static $ObjectDirectories = array();
2376  private static $SessionLifetime = 1440;
2377 
2382  private $NoTSR = FALSE;
2383 
2384  private $KnownPeriodicEvents = array();
2385  private $PeriodicEvents = array(
2386  "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
2387  "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
2388  "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
2389  "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
2390  "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
2391  );
2392  private $EventPeriods = array(
2393  "EVENT_HOURLY" => 3600,
2394  "EVENT_DAILY" => 86400,
2395  "EVENT_WEEKLY" => 604800,
2396  "EVENT_MONTHLY" => 2592000,
2397  "EVENT_PERIODIC" => 0,
2398  );
2399  private $UIEvents = array(
2400  "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
2401  "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
2402  "EVENT_PHP_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
2403  "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
2404  "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
2405  "EVENT_PAGE_OUTPUT_FILTER" => self::EVENTTYPE_CHAIN,
2406  );
2407 
2411  private function LoadSettings()
2412  {
2413  # read settings in from database
2414  $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
2415  $this->Settings = $this->DB->FetchRow();
2416 
2417  # if settings were not previously initialized
2418  if (!$this->Settings)
2419  {
2420  # initialize settings in database
2421  $this->DB->Query("INSERT INTO ApplicationFrameworkSettings"
2422  ." (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
2423 
2424  # read new settings in from database
2425  $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
2426  $this->Settings = $this->DB->FetchRow();
2427  }
2428 
2429  # if base path was not previously set or we appear to have moved
2430  if (!array_key_exists("BasePath", $this->Settings)
2431  || (!strlen($this->Settings["BasePath"]))
2432  || (!array_key_exists("BasePathCheck", $this->Settings))
2433  || (__FILE__ != $this->Settings["BasePathCheck"]))
2434  {
2435  # attempt to extract base path from Apache .htaccess file
2436  if (is_readable(".htaccess"))
2437  {
2438  $Lines = file(".htaccess");
2439  foreach ($Lines as $Line)
2440  {
2441  if (preg_match("/\\s*RewriteBase\\s+/", $Line))
2442  {
2443  $Pieces = preg_split(
2444  "/\\s+/", $Line, NULL, PREG_SPLIT_NO_EMPTY);
2445  $BasePath = $Pieces[1];
2446  }
2447  }
2448  }
2449 
2450  # if base path was found
2451  if (isset($BasePath))
2452  {
2453  # save base path locally
2454  $this->Settings["BasePath"] = $BasePath;
2455 
2456  # save base path to database
2457  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
2458  ." SET BasePath = '".addslashes($BasePath)."'"
2459  .", BasePathCheck = '".addslashes(__FILE__)."'");
2460  }
2461  }
2462 
2463  # if template location cache has been saved to database
2464  if (isset($this->Settings["TemplateLocationCache"]))
2465  {
2466  # unserialize cache values into array and use if valid
2467  $Cache = unserialize($this->Settings["TemplateLocationCache"]);
2468  $this->Settings["TemplateLocationCache"] =
2469  count($Cache) ? $Cache : array();
2470  }
2471  else
2472  {
2473  # start with empty cache
2474  $this->Settings["TemplateLocationCache"] = array();
2475  }
2476  }
2477 
2484  private function RewriteCleanUrls($PageName)
2485  {
2486  # if URL rewriting is supported by the server
2487  if ($this->HtaccessSupport())
2488  {
2489  # retrieve current URL and remove base path if present
2490  $Url = $this->GetPageLocation();
2491 
2492  # for each clean URL mapping
2493  foreach ($this->CleanUrlMappings as $Info)
2494  {
2495  # if current URL matches clean URL pattern
2496  if (preg_match($Info["Pattern"], $Url, $Matches))
2497  {
2498  # set new page
2499  $PageName = $Info["Page"];
2500 
2501  # if $_GET variables specified for clean URL
2502  if ($Info["GetVars"] !== NULL)
2503  {
2504  # for each $_GET variable specified for clean URL
2505  foreach ($Info["GetVars"] as $VarName => $VarTemplate)
2506  {
2507  # start with template for variable value
2508  $Value = $VarTemplate;
2509 
2510  # for each subpattern matched in current URL
2511  foreach ($Matches as $Index => $Match)
2512  {
2513  # if not first (whole) match
2514  if ($Index > 0)
2515  {
2516  # make any substitutions in template
2517  $Value = str_replace("$".$Index, $Match, $Value);
2518  }
2519  }
2520 
2521  # set $_GET variable
2522  $_GET[$VarName] = $Value;
2523  }
2524  }
2525 
2526  # set flag indicating clean URL mapped
2527  $this->CleanUrlRewritePerformed = TRUE;
2528 
2529  # stop looking for a mapping
2530  break;
2531  }
2532  }
2533  }
2534 
2535  # return (possibly) updated page name to caller
2536  return $PageName;
2537  }
2538 
2555  private function FindFile($DirectoryList, $BaseName,
2556  $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
2557  {
2558  # generate template cache index for this page
2559  $CacheIndex = md5(serialize($DirectoryList))
2560  .":".$this->ActiveUI.":".$BaseName;
2561 
2562  # if we have cached location and cache expiration time has not elapsed
2563  if (($this->Settings["TemplateLocationCacheInterval"] > 0)
2564  && count($this->Settings["TemplateLocationCache"])
2565  && array_key_exists($CacheIndex,
2566  $this->Settings["TemplateLocationCache"])
2567  && (time() < strtotime(
2568  $this->Settings["TemplateLocationCacheExpiration"])))
2569  {
2570  # use template location from cache
2571  $FoundFileName = $this->Settings[
2572  "TemplateLocationCache"][$CacheIndex];
2573  }
2574  else
2575  {
2576  # if suffixes specified and base name does not include suffix
2577  if (count($PossibleSuffixes)
2578  && !preg_match("/\.[a-zA-Z0-9]+$/", $BaseName))
2579  {
2580  # add versions of file names with suffixes to file name list
2581  $FileNames = array();
2582  foreach ($PossibleSuffixes as $Suffix)
2583  {
2584  $FileNames[] = $BaseName.".".$Suffix;
2585  }
2586  }
2587  else
2588  {
2589  # use base name as file name
2590  $FileNames = array($BaseName);
2591  }
2592 
2593  # if prefixes specified
2594  if (count($PossiblePrefixes))
2595  {
2596  # add versions of file names with prefixes to file name list
2597  $NewFileNames = array();
2598  foreach ($FileNames as $FileName)
2599  {
2600  foreach ($PossiblePrefixes as $Prefix)
2601  {
2602  $NewFileNames[] = $Prefix.$FileName;
2603  }
2604  }
2605  $FileNames = $NewFileNames;
2606  }
2607 
2608  # for each possible location
2609  $FoundFileName = NULL;
2610  foreach ($DirectoryList as $Dir)
2611  {
2612  # substitute active UI name into path
2613  $Dir = str_replace("%ACTIVEUI%", $this->ActiveUI, $Dir);
2614 
2615  # for each possible file name
2616  foreach ($FileNames as $File)
2617  {
2618  # if template is found at location
2619  if (file_exists($Dir.$File))
2620  {
2621  # save full template file name and stop looking
2622  $FoundFileName = $Dir.$File;
2623  break 2;
2624  }
2625  }
2626  }
2627 
2628  # save location in cache
2629  $this->Settings["TemplateLocationCache"][$CacheIndex]
2630  = $FoundFileName;
2631 
2632  # set flag indicating that cache should be saved
2633  $this->SaveTemplateLocationCache = TRUE;
2634  }
2635 
2636  # return full template file name to caller
2637  return $FoundFileName;
2638  }
2639 
2646  private function GetRequiredFilesNotYetLoaded($PageContentFile)
2647  {
2648  # start out assuming no files required
2649  $RequiredFiles = array();
2650 
2651  # if page content file supplied
2652  if ($PageContentFile)
2653  {
2654  # if file containing list of required files is available
2655  $Path = dirname($PageContentFile);
2656  $RequireListFile = $Path."/REQUIRES";
2657  if (file_exists($RequireListFile))
2658  {
2659  # read in list of required files
2660  $RequestedFiles = file($RequireListFile);
2661 
2662  # for each line in required file list
2663  foreach ($RequestedFiles as $Line)
2664  {
2665  # if line is not a comment
2666  $Line = trim($Line);
2667  if (!preg_match("/^#/", $Line))
2668  {
2669  # if file has not already been loaded
2670  if (!in_array($Line, $this->FoundUIFiles))
2671  {
2672  # add to list of required files
2673  $RequiredFiles[] = $Line;
2674  }
2675  }
2676  }
2677  }
2678  }
2679 
2680  # add in additional required files if any
2681  if (count($this->AdditionalRequiredUIFiles))
2682  {
2683  # make sure there are no duplicates
2684  $AdditionalRequiredUIFiles = array_unique(
2685  $this->AdditionalRequiredUIFiles);
2686 
2687  $RequiredFiles = array_merge(
2688  $RequiredFiles, $AdditionalRequiredUIFiles);
2689  }
2690 
2691  # return list of required files to caller
2692  return $RequiredFiles;
2693  }
2694 
2698  private function SetUpObjectAutoloading()
2699  {
2701  function __autoload($ClassName)
2702  {
2703  ApplicationFramework::AutoloadObjects($ClassName);
2704  }
2705  }
2706 
2712  static function AutoloadObjects($ClassName)
2713  {
2714  static $FileLists;
2715  foreach (self::$ObjectDirectories as $Location => $Info)
2716  {
2717  if (is_dir($Location))
2718  {
2719  $NewClassName = ($Info["ClassPattern"] && $Info["ClassReplacement"])
2720  ? preg_replace($Info["ClassPattern"],
2721  $Info["ClassReplacement"], $ClassName)
2722  : $ClassName;
2723  if (!isset($FileLists[$Location]))
2724  {
2725  $FileLists[$Location] = self::ReadDirectoryTree(
2726  $Location, '/^.+\.php$/i');
2727  }
2728  $FileNames = $FileLists[$Location];
2729  $TargetName = strtolower($Info["Prefix"].$NewClassName.".php");
2730  foreach ($FileNames as $FileName)
2731  {
2732  if (strtolower($FileName) == $TargetName)
2733  {
2734  require_once($Location.$FileName);
2735  break 2;
2736  }
2737  }
2738  }
2739  }
2740  }
2741 
2749  private static function ReadDirectoryTree($Directory, $Pattern)
2750  {
2751  $CurrentDir = getcwd();
2752  chdir($Directory);
2753  $DirIter = new RecursiveDirectoryIterator(".");
2754  $IterIter = new RecursiveIteratorIterator($DirIter);
2755  $RegexResults = new RegexIterator($IterIter, $Pattern,
2756  RecursiveRegexIterator::GET_MATCH);
2757  $FileList = array();
2758  foreach ($RegexResults as $Result)
2759  {
2760  $FileList[] = substr($Result[0], 2);
2761  }
2762  chdir($CurrentDir);
2763  return $FileList;
2764  }
2770  private function UndoMagicQuotes()
2771  {
2772  # if this PHP version has magic quotes support
2773  if (version_compare(PHP_VERSION, "5.4.0", "<"))
2774  {
2775  # turn off runtime magic quotes if on
2776  if (get_magic_quotes_runtime())
2777  {
2778  set_magic_quotes_runtime(FALSE);
2779  }
2780 
2781  # if magic quotes GPC is on
2782  if (get_magic_quotes_gpc())
2783  {
2784  # strip added slashes from incoming variables
2785  $GPC = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
2786  array_walk_recursive($GPC,
2787  array($this, "UndoMagicQuotes_StripCallback"));
2788  }
2789  }
2790  }
2791  private function UndoMagicQuotes_StripCallback(&$Value, $Key)
2792  {
2793  $Value = stripslashes($Value);
2794  }
2795 
2800  private function LoadUIFunctions()
2801  {
2802  $Dirs = array(
2803  "local/interface/%ACTIVEUI%/include",
2804  "interface/%ACTIVEUI%/include",
2805  "local/interface/default/include",
2806  "interface/default/include",
2807  );
2808  foreach ($Dirs as $Dir)
2809  {
2810  $Dir = str_replace("%ACTIVEUI%", $this->ActiveUI, $Dir);
2811  if (is_dir($Dir))
2812  {
2813  $FileNames = scandir($Dir);
2814  foreach ($FileNames as $FileName)
2815  {
2816  if (preg_match("/^F-([A-Za-z_]+)\.php/", $FileName, $Matches)
2817  || preg_match("/^F-([A-Za-z_]+)\.html/", $FileName, $Matches))
2818  {
2819  if (!function_exists($Matches[1]))
2820  {
2821  include_once($Dir."/".$FileName);
2822  }
2823  }
2824  }
2825  }
2826  }
2827  }
2828 
2834  private function ProcessPeriodicEvent($EventName, $Callback)
2835  {
2836  # retrieve last execution time for event if available
2837  $Signature = self::GetCallbackSignature($Callback);
2838  $LastRun = $this->DB->Query("SELECT LastRunAt FROM PeriodicEvents"
2839  ." WHERE Signature = '".addslashes($Signature)."'", "LastRunAt");
2840 
2841  # determine whether enough time has passed for event to execute
2842  $ShouldExecute = (($LastRun === NULL)
2843  || (time() > (strtotime($LastRun) + $this->EventPeriods[$EventName])))
2844  ? TRUE : FALSE;
2845 
2846  # if event should run
2847  if ($ShouldExecute)
2848  {
2849  # add event to task queue
2850  $WrapperCallback = array("ApplicationFramework", "PeriodicEventWrapper");
2851  $WrapperParameters = array(
2852  $EventName, $Callback, array("LastRunAt" => $LastRun));
2853  $this->QueueUniqueTask($WrapperCallback, $WrapperParameters);
2854  }
2855 
2856  # add event to list of periodic events
2857  $this->KnownPeriodicEvents[$Signature] = array(
2858  "Period" => $EventName,
2859  "Callback" => $Callback,
2860  "Queued" => $ShouldExecute);
2861  }
2862 
2870  private static function PeriodicEventWrapper($EventName, $Callback, $Parameters)
2871  {
2872  static $DB;
2873  if (!isset($DB)) { $DB = new Database(); }
2874 
2875  # run event
2876  $ReturnVal = call_user_func_array($Callback, $Parameters);
2877 
2878  # if event is already in database
2879  $Signature = self::GetCallbackSignature($Callback);
2880  if ($DB->Query("SELECT COUNT(*) AS EventCount FROM PeriodicEvents"
2881  ." WHERE Signature = '".addslashes($Signature)."'", "EventCount"))
2882  {
2883  # update last run time for event
2884  $DB->Query("UPDATE PeriodicEvents SET LastRunAt = "
2885  .(($EventName == "EVENT_PERIODIC")
2886  ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
2887  : "NOW()")
2888  ." WHERE Signature = '".addslashes($Signature)."'");
2889  }
2890  else
2891  {
2892  # add last run time for event to database
2893  $DB->Query("INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES "
2894  ."('".addslashes($Signature)."', "
2895  .(($EventName == "EVENT_PERIODIC")
2896  ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
2897  : "NOW()").")");
2898  }
2899  }
2900 
2906  private static function GetCallbackSignature($Callback)
2907  {
2908  return !is_array($Callback) ? $Callback
2909  : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
2910  ."::".$Callback[1];
2911  }
2912 
2917  private function PrepForTSR()
2918  {
2919  # if HTML has been output and it's time to launch another task
2920  # (only TSR if HTML has been output because otherwise browsers
2921  # may misbehave after connection is closed)
2922  if (($this->JumpToPage || !$this->SuppressHTML)
2923  && (time() > (strtotime($this->Settings["LastTaskRunAt"])
2924  + (ini_get("max_execution_time")
2925  / $this->Settings["MaxTasksRunning"]) + 5))
2926  && $this->GetTaskQueueSize()
2927  && $this->Settings["TaskExecutionEnabled"])
2928  {
2929  # begin buffering output for TSR
2930  ob_start();
2931 
2932  # let caller know it is time to launch another task
2933  return TRUE;
2934  }
2935  else
2936  {
2937  # let caller know it is not time to launch another task
2938  return FALSE;
2939  }
2940  }
2941 
2946  private function LaunchTSR()
2947  {
2948  # set headers to close out connection to browser
2949  if (!$this->NoTSR)
2950  {
2951  ignore_user_abort(TRUE);
2952  header("Connection: close");
2953  header("Content-Length: ".ob_get_length());
2954  }
2955 
2956  # output buffered content
2957  while (ob_get_level()) { ob_end_flush(); }
2958  flush();
2959 
2960  # write out any outstanding data and end HTTP session
2961  session_write_close();
2962 
2963  # set flag indicating that we are now running in background
2964  $this->RunningInBackground = TRUE;
2965 
2966  # if there is still a task in the queue
2967  if ($this->GetTaskQueueSize())
2968  {
2969  # turn on output buffering to (hopefully) record any crash output
2970  ob_start();
2971 
2972  # lock tables and grab last task run time to double check
2973  $this->DB->Query("LOCK TABLES ApplicationFrameworkSettings WRITE");
2974  $this->LoadSettings();
2975 
2976  # if still time to launch another task
2977  if (time() > (strtotime($this->Settings["LastTaskRunAt"])
2978  + (ini_get("max_execution_time")
2979  / $this->Settings["MaxTasksRunning"]) + 5))
2980  {
2981  # update the "last run" time and release tables
2982  $this->DB->Query("UPDATE ApplicationFrameworkSettings"
2983  ." SET LastTaskRunAt = '".date("Y-m-d H:i:s")."'");
2984  $this->DB->Query("UNLOCK TABLES");
2985 
2986  # run tasks while there is a task in the queue and enough time left
2987  do
2988  {
2989  # run the next task
2990  $this->RunNextTask();
2991  }
2992  while ($this->GetTaskQueueSize()
2993  && ($this->GetSecondsBeforeTimeout() > 65));
2994  }
2995  else
2996  {
2997  # release tables
2998  $this->DB->Query("UNLOCK TABLES");
2999  }
3000  }
3001  }
3002 
3010  private function GetTaskList($DBQuery, $Count, $Offset)
3011  {
3012  $this->DB->Query($DBQuery." LIMIT ".intval($Offset).",".intval($Count));
3013  $Tasks = array();
3014  while ($Row = $this->DB->FetchRow())
3015  {
3016  $Tasks[$Row["TaskId"]] = $Row;
3017  if ($Row["Callback"] ==
3018  serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
3019  {
3020  $WrappedCallback = unserialize($Row["Parameters"]);
3021  $Tasks[$Row["TaskId"]]["Callback"] = $WrappedCallback[1];
3022  $Tasks[$Row["TaskId"]]["Parameters"] = NULL;
3023  }
3024  else
3025  {
3026  $Tasks[$Row["TaskId"]]["Callback"] = unserialize($Row["Callback"]);
3027  $Tasks[$Row["TaskId"]]["Parameters"] = unserialize($Row["Parameters"]);
3028  }
3029  }
3030  return $Tasks;
3031  }
3032 
3036  private function RunNextTask()
3037  {
3038  # lock tables to prevent same task from being run by multiple sessions
3039  $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
3040 
3041  # look for task at head of queue
3042  $this->DB->Query("SELECT * FROM TaskQueue ORDER BY Priority, TaskId LIMIT 1");
3043  $Task = $this->DB->FetchRow();
3044 
3045  # if there was a task available
3046  if ($Task)
3047  {
3048  # move task from queue to running tasks list
3049  $this->DB->Query("INSERT INTO RunningTasks "
3050  ."(TaskId,Callback,Parameters,Priority,Description) "
3051  ."SELECT * FROM TaskQueue WHERE TaskId = "
3052  .intval($Task["TaskId"]));
3053  $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = "
3054  .intval($Task["TaskId"]));
3055 
3056  # release table locks to again allow other sessions to run tasks
3057  $this->DB->Query("UNLOCK TABLES");
3058 
3059  # unpack stored task info
3060  $Callback = unserialize($Task["Callback"]);
3061  $Parameters = unserialize($Task["Parameters"]);
3062 
3063  # attempt to load task callback if not already available
3064  $this->LoadFunction($Callback);
3065 
3066  # run task
3067  $this->RunningTask = $Task;
3068  if ($Parameters)
3069  {
3070  call_user_func_array($Callback, $Parameters);
3071  }
3072  else
3073  {
3074  call_user_func($Callback);
3075  }
3076  unset($this->RunningTask);
3077 
3078  # remove task from running tasks list
3079  $this->DB->Query("DELETE FROM RunningTasks"
3080  ." WHERE TaskId = ".intval($Task["TaskId"]));
3081 
3082  # prune running tasks list if necessary
3083  $RunningTasksCount = $this->DB->Query(
3084  "SELECT COUNT(*) AS TaskCount FROM RunningTasks", "TaskCount");
3085  if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
3086  {
3087  $this->DB->Query("DELETE FROM RunningTasks ORDER BY StartedAt"
3088  ." LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
3089  }
3090  }
3091  else
3092  {
3093  # release table locks to again allow other sessions to run tasks
3094  $this->DB->Query("UNLOCK TABLES");
3095  }
3096  }
3097 
3103  function OnCrash()
3104  {
3105  if (isset($this->RunningTask))
3106  {
3107  if (function_exists("error_get_last"))
3108  {
3109  $CrashInfo["LastError"] = error_get_last();
3110  }
3111  if (ob_get_length() !== FALSE)
3112  {
3113  $CrashInfo["OutputBuffer"] = ob_get_contents();
3114  }
3115  if (isset($CrashInfo))
3116  {
3117  $DB = new Database();
3118  $DB->Query("UPDATE RunningTasks SET CrashInfo = '"
3119  .addslashes(serialize($CrashInfo))
3120  ."' WHERE TaskId = ".intval($this->RunningTask["TaskId"]));
3121  }
3122  }
3123 
3124  print("\n");
3125  return;
3126  }
3127 
3144  private function AddToDirList($DirList, $Dir, $SearchLast, $SkipSlashCheck)
3145  {
3146  # convert incoming directory to array of directories (if needed)
3147  $Dirs = is_array($Dir) ? $Dir : array($Dir);
3148 
3149  # reverse array so directories are searched in specified order
3150  $Dirs = array_reverse($Dirs);
3151 
3152  # for each directory
3153  foreach ($Dirs as $Location)
3154  {
3155  # make sure directory includes trailing slash
3156  if (!$SkipSlashCheck)
3157  {
3158  $Location = $Location
3159  .((substr($Location, -1) != "/") ? "/" : "");
3160  }
3161 
3162  # remove directory from list if already present
3163  if (in_array($Location, $DirList))
3164  {
3165  $DirList = array_diff(
3166  $DirList, array($Location));
3167  }
3168 
3169  # add directory to list of directories
3170  if ($SearchLast)
3171  {
3172  array_push($DirList, $Location);
3173  }
3174  else
3175  {
3176  array_unshift($DirList, $Location);
3177  }
3178  }
3179 
3180  # return updated directory list to caller
3181  return $DirList;
3182  }
3183 
3191  private function ArrayPermutations($Items, $Perms = array())
3192  {
3193  if (empty($Items))
3194  {
3195  $Result = array($Perms);
3196  }
3197  else
3198  {
3199  $Result = array();
3200  for ($Index = count($Items) - 1; $Index >= 0; --$Index)
3201  {
3202  $NewItems = $Items;
3203  $NewPerms = $Perms;
3204  list($Segment) = array_splice($NewItems, $Index, 1);
3205  array_unshift($NewPerms, $Segment);
3206  $Result = array_merge($Result,
3207  $this->ArrayPermutations($NewItems, $NewPerms));
3208  }
3209  }
3210  return $Result;
3211  }
3212 
3219  private function OutputModificationCallbackShell($Matches)
3220  {
3221  # call previously-stored external function
3222  return call_user_func($this->OutputModificationCallbackInfo["Callback"],
3223  $Matches,
3224  $this->OutputModificationCallbackInfo["Pattern"],
3225  $this->OutputModificationCallbackInfo["Page"],
3226  $this->OutputModificationCallbackInfo["SearchPattern"]);
3227  }
3228 
3230  private $InterfaceDirList = array(
3231  "local/interface/%ACTIVEUI%/",
3232  "interface/%ACTIVEUI%/",
3233  "local/interface/default/",
3234  "interface/default/",
3235  );
3240  private $IncludeDirList = array(
3241  "local/interface/%ACTIVEUI%/include/",
3242  "interface/%ACTIVEUI%/include/",
3243  "local/interface/default/include/",
3244  "interface/default/include/",
3245  );
3247  private $ImageDirList = array(
3248  "local/interface/%ACTIVEUI%/images/",
3249  "interface/%ACTIVEUI%/images/",
3250  "local/interface/default/images/",
3251  "interface/default/images/",
3252  );
3254  private $FunctionDirList = array(
3255  "local/interface/%ACTIVEUI%/include/",
3256  "interface/%ACTIVEUI%/include/",
3257  "local/interface/default/include/",
3258  "interface/default/include/",
3259  "local/include/",
3260  "include/",
3261  );
3262 
3263  const NOVALUE = ".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
3264 };
const LOGLVL_ERROR
ERROR error logging level.
LoggingLevel($NewValue=NULL)
Get/set 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 not be executed after buffered content has been output and that won&#39;t have i...
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.
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.
GetTaskQueueSize($Priority=NULL)
Retrieve current number of tasks in queue.
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.
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.
TaskExecutionEnabled($NewValue=NULL)
Get/set whether automatic task execution is enabled.
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.
TemplateLocationCacheExpirationInterval($NewInterval=-1)
Get/set UI template location cache expiration period in minutes.
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.
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.
GetTask($TaskId)
Retrieve task info from queue (either running or queued tasks).
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.
MaxTasks($NewValue=NULL)
Get/set maximum number of tasks to have running simultaneously.
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.
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.