3 # FILE: ApplicationFramework.php 5 # Part of the ScoutLib application support library 6 # Copyright 2009-2016 Edward Almasy and Internet Scout Research Group 7 # http://scout.wisc.edu 17 # ---- PUBLIC INTERFACE -------------------------------------------------- 25 public function __construct()
27 # set up a class alias for convenience 28 class_alias(
"ApplicationFramework",
"AF");
30 # make sure default time zone is set 31 # (using CST if nothing set because we have to use something 32 # and Scout is based in Madison, WI, which is in CST) 33 if ((ini_get(
"date.timezone") === NULL)
34 || !strlen(ini_get(
"date.timezone")))
36 ini_set(
"date.timezone",
"America/Chicago");
39 # save execution start time 40 $this->ExecutionStartTime = microtime(TRUE);
42 # set up default object file search locations 43 self::AddObjectDirectory(
"local/interface/%ACTIVEUI%/objects");
44 self::AddObjectDirectory(
"interface/%ACTIVEUI%/objects");
45 self::AddObjectDirectory(
"local/interface/%DEFAULTUI%/objects");
46 self::AddObjectDirectory(
"interface/%DEFAULTUI%/objects");
47 self::AddObjectDirectory(
"local/objects");
48 self::AddObjectDirectory(
"objects");
50 # set up object file autoloader 51 spl_autoload_register(array($this,
"AutoloadObjects"));
53 # set up function to output any buffered text in case of crash 54 register_shutdown_function(array($this,
"OnCrash"));
56 # if we were not invoked via command line interface 57 if (php_sapi_name() !==
"cli")
59 # build cookie domain string 60 $SessionDomain = isset($_SERVER[
"SERVER_NAME"]) ? $_SERVER[
"SERVER_NAME"]
61 : isset($_SERVER[
"HTTP_HOST"]) ? $_SERVER[
"HTTP_HOST"]
64 # include a leading period so that older browsers implementing 65 # rfc2109 do not reject our cookie 66 $SessionDomain =
".".$SessionDomain;
68 # if it appears our session storage area is writable 69 if (is_writable(session_save_path()))
71 # store our session files in a subdirectory to avoid 72 # accidentally sharing sessions with other installations 74 $SessionStorage = session_save_path()
75 .
"/".self::$AppName.
"_".md5($SessionDomain.dirname(__FILE__));
77 # create session storage subdirectory if not found 78 if (!is_dir($SessionStorage)) { mkdir($SessionStorage, 0700 ); }
80 # if session storage subdirectory is writable 81 if (is_writable($SessionStorage))
83 # save parameters of our session storage as instance variables 85 $this->SessionGcProbability =
86 ini_get(
"session.gc_probability") / ini_get(
"session.gc_divisor");
87 # require a gc probability of at least MIN_GC_PROBABILITY 88 if ($this->SessionGcProbability < self::MIN_GC_PROBABILITY)
90 $this->SessionGcProbability = self::MIN_GC_PROBABILITY;
93 $this->SessionStorage = $SessionStorage;
95 # set the new session storage location 96 session_save_path($SessionStorage);
98 # disable PHP's garbage collection, as it does not handle 99 # subdirectories (instead, we'll do the cleanup as we run 101 ini_set(
"session.gc_probability", 0);
105 # set garbage collection max period to our session lifetime 106 ini_set(
"session.gc_maxlifetime", self::$SessionLifetime);
108 # limit cookie to secure connection if we are running over same 109 $SecureCookie = isset($_SERVER[
"HTTPS"]) ? TRUE : FALSE;
111 # Cookies lacking embedded dots are... fun. 112 # rfc2109 sec 4.3.2 says to reject them 113 # rfc2965 sec 3.3.2 says to reject them 114 # rfc6265 sec 4.1.2.3 says only that "public suffixes" 115 # should be rejected. They reference Mozilla's 116 # publicsuffix.org, which does not contain 'localhost'. 117 # However, empirically in early 2017 Firefox still rejects 119 # Therefore, don't set a cookie domain if we're running on 120 # localhost to avoid this problem. 121 if (preg_match(
'/^\.localhost(:[0-9]+)?$/', $SessionDomain))
125 session_set_cookie_params(self::$SessionLifetime,
"/",
126 $SessionDomain, $SecureCookie, TRUE);
128 # attempt to start session 129 $SessionStarted = @session_start();
131 # if session start failed 132 if (!$SessionStarted)
134 # regenerate session ID and attempt to start session again 135 session_regenerate_id(TRUE);
139 # bump up our cookie expiry time, so that it'll die 140 # $SessionLifetime from now, rather than $SessionLifetime 141 # from whenever we created it 143 session_name(), session_id(),
144 time() + self::$SessionLifetime,
"/",
145 $SessionDomain, $SecureCookie, TRUE);
148 # set up our internal environment 151 # set up our exception handler 152 set_exception_handler(array($this,
"GlobalExceptionHandler"));
154 # load our settings from database 155 $this->LoadSettings();
157 # set PHP maximum execution time 158 ini_set(
"max_execution_time", $this->Settings[
"MaxExecTime"]);
159 set_time_limit($this->Settings[
"MaxExecTime"]);
161 # register events we handle internally 165 # attempt to create SCSS cache directory if needed and it does not exist 167 { @mkdir(self::$ScssCacheDir, 0777, TRUE); }
169 # attempt to create minimized JS cache directory if needed and it does not exist 172 && !is_dir(self::$JSMinCacheDir))
174 @mkdir(self::$JSMinCacheDir, 0777, TRUE);
183 public function __destruct()
185 # if template location cache is flagged to be saved 186 if ($this->SaveTemplateLocationCache)
188 # write template location cache out and update cache expiration 189 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings" 190 .
" SET TemplateLocationCache = '" 191 .addslashes(serialize(
192 $this->TemplateLocationCache)).
"'," 193 .
" TemplateLocationCacheExpiration = '" 195 $this->TemplateLocationCacheExpiration).
"'");
198 # if object location cache is flagged to be saved 199 if (self::$SaveObjectLocationCache)
201 # write object location cache out and update cache expiration 202 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings" 203 .
" SET ObjectLocationCache = '" 204 .addslashes(serialize(
205 self::$ObjectLocationCache)).
"'," 206 .
" ObjectLocationCacheExpiration = '" 208 self::$ObjectLocationCacheExpiration).
"'");
218 public function GlobalExceptionHandler($Exception)
220 # display exception info 221 $Message = $Exception->getMessage();
222 $Location = str_replace(getcwd().
"/",
"",
223 $Exception->getFile().
"[".$Exception->getLine().
"]");
224 $Trace = preg_replace(
":(#[0-9]+) ".getcwd().
"/".
":",
"$1 ",
225 $Exception->getTraceAsString());
226 if (php_sapi_name() ==
"cli")
228 print
"Uncaught Exception\n" 229 .
"Message: ".$Message.
"\n" 230 .
"Location: ".$Location.
"\n" 236 ?><table width=
"100%" cellpadding=
"5" 237 style=
"border: 2px solid #666666; background: #CCCCCC; 238 font-family: Courier New, Courier, monospace; 239 margin-top: 10px;"><tr><td>
240 <div style=
"color: #666666;">
241 <span style=
"font-size: 150%;">
242 <b>Uncaught Exception</b></span><br />
243 <b>
Message:</b> <i><?= $Message ?></i><br />
244 <b>Location:</b> <i><?= $Location ?></i><br />
245 <b>Trace:</b> <blockquote><pre><?= $Trace ?></pre></blockquote>
247 </td></tr></table><?
PHP 250 # log exception if not running from command line 251 if (php_sapi_name() !==
"cli")
253 $TraceString = $Exception->getTraceAsString();
254 $TraceString = str_replace(
"\n",
", ", $TraceString);
255 $TraceString = preg_replace(
":(#[0-9]+) ".getcwd().
"/".
":",
256 "$1 ", $TraceString);
257 $LogMsg =
"Uncaught exception (".$Exception->getMessage().
")" 258 .
" at ".$Location.
"." 259 .
" TRACE: ".$TraceString
261 $this->
LogError(self::LOGLVL_ERROR, $LogMsg);
285 $Dir, $NamespacePrefixes = array(), $Callback = NULL)
287 # check to make sure any supplied callback looks valid 288 if (!is_null($Callback) && !is_callable($Callback))
290 throw new InvalidArgumentException(
"Supplied callback (\"" 291 .$Callback.
"\") is invalid.");
294 # make sure directory has trailing slash 295 $Dir = $Dir.((substr($Dir, -1) !=
"/") ?
"/" :
"");
297 # make sure namespaces is an array 298 if (!is_array($NamespacePrefixes))
300 if (is_string($NamespacePrefixes))
302 $NamespacePrefixes = [ $NamespacePrefixes ];
306 throw new InvalidArgumentException(
"Supplied namespace (\"" 307 .$NamespacePrefixes.
"\") is invalid.");
311 # make sure namespace prefixes are in decreasing order of length 312 usort($NamespacePrefixes,
function($A, $B)
314 return strlen($B) - strlen($A);
317 # add directory to directory list 318 self::$ObjectDirectories[$Dir] = array(
319 "NamespacePrefixes" => $NamespacePrefixes,
320 "Callback" => $Callback,
344 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
346 # add directories to existing image directory list 347 $this->ImageDirList = $this->AddToDirList(
348 $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
372 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
374 # add directories to existing image directory list 375 $this->IncludeDirList = $this->AddToDirList(
376 $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
399 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
401 # add directories to existing image directory list 402 $this->InterfaceDirList = $this->AddToDirList(
403 $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
405 # cleared cached lists for user interfaces 406 self::$UserInterfaceListCache = array();
407 self::$UserInterfacePathsCache = array();
430 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
432 # add directories to existing image directory list 433 $this->FunctionDirList = $this->AddToDirList(
434 $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
444 $this->BrowserDetectFunc = $DetectionFunc;
455 if (is_callable($Callback))
457 $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
471 $NewInterval =
DB_NOVALUE, $Persistent = FALSE)
473 return $this->UpdateSetting(
474 "TemplateLocationCacheInterval", $NewInterval, $Persistent);
482 $this->TemplateLocationCache = array();
483 $this->SaveTemplateLocationCache = TRUE;
496 $NewInterval =
DB_NOVALUE, $Persistent = FALSE)
498 return $this->UpdateSetting(
499 "ObjectLocationCacheInterval", $NewInterval, $Persistent);
507 self::$ObjectLocationCache = array();
508 self::$SaveObjectLocationCache = TRUE;
522 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
537 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
553 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
569 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
586 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
603 $BacktraceOptions = 0, $BacktraceLimit = 0)
605 if (version_compare(PHP_VERSION,
"5.4.0",
">="))
607 $this->SavedContext = debug_backtrace(
608 $BacktraceOptions, $BacktraceLimit);
612 $this->SavedContext = debug_backtrace($BacktraceOptions);
614 array_shift($this->SavedContext);
623 # perform any clean URL rewriting 624 $PageName = $this->RewriteCleanUrls($PageName);
626 # sanitize incoming page name and save local copy 627 $PageName = preg_replace(
"/[^a-zA-Z0-9_.-]/",
"", $PageName);
628 $this->PageName = $PageName;
630 # if page caching is turned on 633 # if we have a cached page 634 $CachedPage = $this->CheckForCachedPage($PageName);
635 if ($CachedPage !== NULL)
637 # set header to indicate cache hit was found 638 header(
"X-ScoutAF-Cache: HIT");
640 # display cached page and exit 646 # set header to indicate no cache hit was found 647 header(
"X-ScoutAF-Cache: MISS");
651 # buffer any output from includes or PHP file 654 # include any files needed to set up execution environment 655 $IncludeFileContext = array();
656 foreach ($this->EnvIncludes as $IncludeFile)
658 $IncludeFileContext = $this->FilterContext(self::CONTEXT_ENV,
659 self::IncludeFile($IncludeFile, $IncludeFileContext));
663 $this->
SignalEvent(
"EVENT_PAGE_LOAD", array(
"PageName" => $PageName));
665 # signal PHP file load 666 $SignalResult = $this->
SignalEvent(
"EVENT_PHP_FILE_LOAD", array(
667 "PageName" => $PageName));
669 # if signal handler returned new page name value 670 $NewPageName = $PageName;
671 if (($SignalResult[
"PageName"] != $PageName)
672 && strlen($SignalResult[
"PageName"]))
674 # if new page name value is page file 675 if (file_exists($SignalResult[
"PageName"]))
677 # use new value for PHP file name 678 $PageFile = $SignalResult[
"PageName"];
682 # use new value for page name 683 $NewPageName = $SignalResult[
"PageName"];
686 # update local copy of page name 687 $this->PageName = $NewPageName;
690 # if we do not already have a PHP file 691 if (!isset($PageFile))
693 # look for PHP file for page 694 $OurPageFile =
"pages/".$NewPageName.
".php";
695 $LocalPageFile =
"local/pages/".$NewPageName.
".php";
696 $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
697 : (file_exists($OurPageFile) ? $OurPageFile
698 :
"pages/".$this->DefaultPage.
".php");
702 $IncludeFileContext = $this->FilterContext(self::CONTEXT_PAGE,
703 self::IncludeFile($PageFile, $IncludeFileContext));
705 # save buffered output to be displayed later after HTML file loads 706 $PageOutput = ob_get_contents();
709 # signal PHP file load is complete 711 $Context[
"Variables"] = $IncludeFileContext;
713 array(
"PageName" => $PageName,
"Context" => $Context));
714 $PageCompleteOutput = ob_get_contents();
717 # set up for possible TSR (Terminate and Stay Resident :)) 718 $ShouldTSR = $this->PrepForTSR();
720 # if PHP file indicated we should autorefresh to somewhere else 721 if (($this->JumpToPage) && ($this->JumpToPageDelay == 0))
723 if (!strlen(trim($PageOutput)))
725 # if client supports HTTP/1.1, use a 303 as it is most accurate 726 if ($_SERVER[
"SERVER_PROTOCOL"] ==
"HTTP/1.1")
728 header(
"HTTP/1.1 303 See Other");
729 header(
"Location: ".$this->JumpToPage);
733 # if the request was an HTTP/1.0 GET or HEAD, then 734 # use a 302 response code. 736 # NB: both RFC 2616 (HTTP/1.1) and RFC1945 (HTTP/1.0) 737 # explicitly prohibit automatic redirection via a 302 738 # if the request was not GET or HEAD. 739 if ($_SERVER[
"SERVER_PROTOCOL"] ==
"HTTP/1.0" &&
740 ($_SERVER[
"REQUEST_METHOD"] ==
"GET" ||
741 $_SERVER[
"REQUEST_METHOD"] ==
"HEAD") )
743 header(
"HTTP/1.0 302 Found");
744 header(
"Location: ".$this->JumpToPage);
747 # otherwise, fall back to a meta refresh 750 print
'<html><head><meta http-equiv="refresh" ' 751 .
'content="0; URL='.$this->JumpToPage.
'">' 752 .
'</head><body></body></html>';
757 # else if HTML loading is not suppressed 758 elseif (!$this->SuppressHTML)
760 # set content-type to get rid of diacritic errors 761 header(
"Content-Type: text/html; charset=" 764 # load common HTML file (defines common functions) if available 765 $CommonHtmlFile = $this->FindFile($this->IncludeDirList,
766 "Common", array(
"tpl",
"html"));
769 $IncludeFileContext = $this->FilterContext(self::CONTEXT_COMMON,
770 self::IncludeFile($CommonHtmlFile, $IncludeFileContext));
774 $this->LoadUIFunctions();
776 # begin buffering content 779 # signal HTML file load 780 $SignalResult = $this->
SignalEvent(
"EVENT_HTML_FILE_LOAD", array(
781 "PageName" => $PageName));
783 # if signal handler returned new page name value 784 $NewPageName = $PageName;
785 $PageContentFile = NULL;
786 if (($SignalResult[
"PageName"] != $PageName)
787 && strlen($SignalResult[
"PageName"]))
789 # if new page name value is HTML file 790 if (file_exists($SignalResult[
"PageName"]))
792 # use new value for HTML file name 793 $PageContentFile = $SignalResult[
"PageName"];
797 # use new value for page name 798 $NewPageName = $SignalResult[
"PageName"];
802 # load page content HTML file if available 803 if ($PageContentFile === NULL)
805 $PageContentFile = $this->FindFile(
806 $this->InterfaceDirList, $NewPageName,
807 array(
"tpl",
"html"));
809 if ($PageContentFile)
811 $IncludeFileContext = $this->FilterContext(self::CONTEXT_INTERFACE,
812 self::IncludeFile($PageContentFile, $IncludeFileContext));
816 print
"<h2>ERROR: No HTML/TPL template found" 817 .
" for this page (".$NewPageName.
").</h2>";
820 # signal HTML file load complete 821 $SignalResult = $this->
SignalEvent(
"EVENT_HTML_FILE_LOAD_COMPLETE");
823 # stop buffering and save output 824 $PageContentOutput = ob_get_contents();
827 # if standard page start/end have not been suppressed 828 $PageStartOutput =
"";
830 if (!$this->SuppressStdPageStartAndEnd)
832 # load page start HTML file if available 833 $PageStartFile = $this->FindFile($this->IncludeDirList,
"Start",
834 array(
"tpl",
"html"), array(
"StdPage",
"StandardPage"));
838 $IncludeFileContext = self::IncludeFile(
839 $PageStartFile, $IncludeFileContext);
840 $PageStartOutput = ob_get_contents();
843 $IncludeFileContext = $this->FilterContext(
844 self::CONTEXT_START, $IncludeFileContext);
846 # load page end HTML file if available 847 $PageEndFile = $this->FindFile($this->IncludeDirList,
"End",
848 array(
"tpl",
"html"), array(
"StdPage",
"StandardPage"));
852 self::IncludeFile($PageEndFile, $IncludeFileContext);
853 $PageEndOutput = ob_get_contents();
858 # clear include file context because it may be large and is no longer needed 859 unset($IncludeFileContext);
861 # if page auto-refresh requested 862 if ($this->JumpToPage)
864 # add auto-refresh tag to page 866 "http-equiv" =>
"refresh",
867 "content" => $this->JumpToPageDelay,
868 "url" => $this->JumpToPage,
873 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
875 # get list of any required files not loaded 876 $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
878 # add file loading tags to page 879 $FullPageOutput = $this->AddFileTagsToPageOutput(
880 $FullPageOutput, $RequiredFiles);
882 # add any requested meta tags to page 883 $FullPageOutput = $this->AddMetaTagsToPageOutput($FullPageOutput);
885 # perform any regular expression replacements in output 886 $NewFullPageOutput = preg_replace($this->OutputModificationPatterns,
887 $this->OutputModificationReplacements, $FullPageOutput);
889 # check to make sure replacements didn't fail 890 $FullPageOutput = $this->CheckOutputModification(
891 $FullPageOutput, $NewFullPageOutput,
892 "regular expression replacements");
894 # for each registered output modification callback 895 foreach ($this->OutputModificationCallbacks as $Info)
897 # set up data for callback 898 $this->OutputModificationCallbackInfo = $Info;
900 # perform output modification 901 $NewFullPageOutput = preg_replace_callback($Info[
"SearchPattern"],
902 array($this,
"OutputModificationCallbackShell"),
905 # check to make sure modification didn't fail 906 $ErrorInfo =
"callback info: ".print_r($Info, TRUE);
907 $FullPageOutput = $this->CheckOutputModification(
908 $FullPageOutput, $NewFullPageOutput, $ErrorInfo);
911 # provide the opportunity to modify full page output 912 $SignalResult = $this->
SignalEvent(
"EVENT_PAGE_OUTPUT_FILTER", array(
913 "PageOutput" => $FullPageOutput));
914 if (isset($SignalResult[
"PageOutput"])
915 && strlen(trim($SignalResult[
"PageOutput"])))
917 $FullPageOutput = $SignalResult[
"PageOutput"];
920 # if relative paths may not work because we were invoked via clean URL 921 if ($this->CleanUrlRewritePerformed || self::WasUrlRewritten())
923 # if using the <base> tag is okay 927 # add <base> tag to header 928 $PageStartOutput = str_replace(
"<head>",
929 "<head><base href=\"".$BaseUrl.
"\" />",
932 # re-assemble full page with new header 933 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
935 # the absolute URL to the current page 938 # make HREF attribute values with just a fragment ID 939 # absolute since they don't work with the <base> tag because 940 # they are relative to the current page/URL, not the site 942 $NewFullPageOutput = preg_replace(
943 array(
"%href=\"(#[^:\" ]+)\"%i",
"%href='(#[^:' ]+)'%i"),
944 array(
"href=\"".$FullUrl.
"$1\"",
"href='".$FullUrl.
"$1'"),
947 # check to make sure HREF cleanup didn't fail 948 $FullPageOutput = $this->CheckOutputModification(
949 $FullPageOutput, $NewFullPageOutput,
954 # try to fix any relative paths throughout code 955 $SrcFileExtensions =
"(js|css|gif|png|jpg|svg|ico)";
956 $RelativePathPatterns = array(
957 "%src=\"/?([^?*:;{}\\\\\" ]+)\.".$SrcFileExtensions.
"\"%i",
958 "%src='/?([^?*:;{}\\\\' ]+)\.".$SrcFileExtensions.
"'%i",
959 # don
't rewrite HREF attributes that are just 960 # fragment IDs because they are relative to the 961 # current page/URL, not the site root 962 "%href=\"/?([^#][^:\" ]*)\"%i", 963 "%href='/?([^#][^:
' ]*)'%i
", 964 "%action=\
"/?([^#][^:\" ]*)\"%i",
965 "%action='/?([^#][^:' ]*)'%i",
966 "%@import\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
967 "%@import\s+url\('/?([^:\" ]+)'\s*\)%i",
968 "%src:\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
969 "%src:\s+url\('/?([^:\" ]+)'\s*\)%i",
970 "%@import\s+\"/?([^:\" ]+)\"\s*%i",
971 "%@import\s+'/?([^:\" ]+)'\s*%i",
973 $RelativePathReplacements = array(
974 "src=\"".$BaseUrl.
"$1.$2\"",
975 "src=\"".$BaseUrl.
"$1.$2\"",
976 "href=\"".$BaseUrl.
"$1\"",
977 "href=\"".$BaseUrl.
"$1\"",
978 "action=\"".$BaseUrl.
"$1\"",
979 "action=\"".$BaseUrl.
"$1\"",
980 "@import url(\"".$BaseUrl.
"$1\")",
981 "@import url('".$BaseUrl.
"$1')",
982 "src: url(\"".$BaseUrl.
"$1\")",
983 "src: url('".$BaseUrl.
"$1')",
984 "@import \"".$BaseUrl.
"$1\"",
985 "@import '".$BaseUrl.
"$1'",
987 $NewFullPageOutput = preg_replace($RelativePathPatterns,
988 $RelativePathReplacements, $FullPageOutput);
990 # check to make sure relative path fixes didn't fail 991 $FullPageOutput = $this->CheckOutputModification(
992 $FullPageOutput, $NewFullPageOutput,
993 "relative path fixes");
997 # handle any necessary alternate domain rewriting 998 $FullPageOutput = $this->RewriteAlternateDomainUrls($FullPageOutput);
1000 # update page cache for this page 1001 $this->UpdatePageCache($PageName, $FullPageOutput);
1003 # write out full page 1004 print $FullPageOutput;
1007 # run any post-processing routines 1008 foreach ($this->PostProcessingFuncs as $Func)
1010 call_user_func_array($Func[
"FunctionName"], $Func[
"Arguments"]);
1013 # write out any output buffered from page code execution 1014 if (strlen($PageOutput))
1016 if (!$this->SuppressHTML)
1018 ?><table width=
"100%" cellpadding=
"5" 1019 style=
"border: 2px solid #666666; background: #CCCCCC; 1020 font-family: Courier New, Courier, monospace; 1021 margin-top: 10px;"><tr><td><?
PHP 1023 if ($this->JumpToPage)
1025 ?><div style=
"color: #666666;"><span style=
"font-size: 150%;">
1026 <b>Page Jump Aborted</b></span>
1027 (because of error or other unexpected output)<br />
1029 <i><?
PHP print($this->JumpToPage); ?></i></div><?
PHP 1032 if (!$this->SuppressHTML)
1034 ?></td></tr></table><?
PHP 1038 # write out any output buffered from the page code execution complete signal 1039 if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
1041 print $PageCompleteOutput;
1044 # log slow page loads 1046 && !$this->DoNotLogSlowPageLoad
1050 $RemoteHost = gethostbyaddr($_SERVER[
"REMOTE_ADDR"]);
1051 if ($RemoteHost === FALSE)
1053 $RemoteHost = $_SERVER[
"REMOTE_ADDR"];
1055 elseif ($RemoteHost != $_SERVER[
"REMOTE_ADDR"])
1057 $RemoteHost .=
" (".$_SERVER[
"REMOTE_ADDR"].
")";
1059 $SlowPageLoadMsg =
"Slow page load (" 1061 .$this->FullUrl().
" from ".$RemoteHost;
1062 $this->
LogMessage(self::LOGLVL_INFO, $SlowPageLoadMsg);
1065 # execute callbacks that should not have their output buffered 1066 foreach ($this->UnbufferedCallbacks as $Callback)
1068 call_user_func_array($Callback[0], $Callback[1]);
1071 # log high memory usage 1072 if (function_exists(
"memory_get_peak_usage"))
1077 && (memory_get_peak_usage(TRUE) >= $MemoryThreshold))
1079 $HighMemUsageMsg =
"High peak memory usage (" 1080 .number_format(memory_get_peak_usage(TRUE)).
") for " 1081 .$this->FullUrl().
" from " 1082 .$_SERVER[
"REMOTE_ADDR"];
1083 $this->
LogMessage(self::LOGLVL_INFO, $HighMemUsageMsg);
1087 $this->UpdateLastUsedTimeForActiveSessions();
1089 # terminate and stay resident (TSR!) if indicated and HTML has been output 1090 # (only TSR if HTML has been output because otherwise browsers will misbehave) 1091 if ($ShouldTSR) { $this->LaunchTSR(); }
1101 return $this->PageName;
1111 # retrieve current URL 1112 $Url = self::GetScriptUrl();
1114 # remove the base path if present 1115 $BasePath = $this->Settings[
"BasePath"];
1116 if (stripos($Url, $BasePath) === 0)
1118 $Url = substr($Url, strlen($BasePath));
1121 # if we're being accessed via an alternate domain, 1122 # add the appropriate prefix in 1124 self::$RootUrlOverride !== NULL)
1126 $VHost = $_SERVER[
"SERVER_NAME"];
1127 if (isset($this->AlternateDomainPrefixes[$VHost]))
1129 $ThisPrefix = $this->AlternateDomainPrefixes[$VHost];
1130 $Url = $ThisPrefix.
"/".$Url;
1162 && (strpos($Page,
"?") === FALSE)
1163 && ((strpos($Page,
"=") !== FALSE)
1164 || ((stripos($Page,
".php") === FALSE)
1165 && (stripos($Page,
".htm") === FALSE)
1166 && (strpos($Page,
"/") === FALSE)))
1167 && (stripos($Page,
"http://") !== 0)
1168 && (stripos($Page,
"https://") !== 0))
1170 $this->JumpToPage = self::BaseUrl() .
"index.php?P=".$Page;
1174 $this->JumpToPage = $Page;
1176 $this->JumpToPageDelay = $Delay;
1185 return ($this->JumpToPage === NULL) ? FALSE : TRUE;
1199 if ($NewSetting !== NULL) { $this->
HtmlCharset = $NewSetting; }
1200 return $this->HtmlCharset;
1214 if (!is_array($File)) { $File = array($File); }
1215 $this->DoNotMinimizeList = array_merge($this->DoNotMinimizeList, $File);
1230 if ($NewValue !== NULL) { $this->
UseBaseTag = $NewValue ? TRUE : FALSE; }
1231 return $this->UseBaseTag;
1243 $this->SuppressHTML = $NewSetting;
1255 $this->SuppressStdPageStartAndEnd = $NewSetting;
1265 if ($UIName !== NULL)
1267 self::$DefaultUI = $UIName;
1269 return self::$DefaultUI;
1280 if ($UIName !== NULL)
1282 self::$ActiveUI = preg_replace(
"/^SPTUI--/",
"", $UIName);
1284 return self::$ActiveUI;
1299 if (!isset(self::$UserInterfaceListCache[$FilterExp]))
1301 # retrieve paths to user interface directories 1304 # start out with an empty list 1305 self::$UserInterfaceListCache[$FilterExp] = array();
1307 # for each possible UI directory 1308 foreach ($Paths as $CanonicalName => $Path)
1310 # if name file available 1311 $LabelFile = $Path.
"/NAME";
1312 if (is_readable($LabelFile))
1315 $Label = file_get_contents($LabelFile);
1317 # if the UI name looks reasonable 1318 if (strlen(trim($Label)))
1321 self::$UserInterfaceListCache[$FilterExp][$CanonicalName] =
1326 # if we do not have a name yet 1327 if (!isset(self::$UserInterfaceListCache[$FilterExp][$CanonicalName]))
1329 # use base directory for name 1330 self::$UserInterfaceListCache[$FilterExp][$CanonicalName] =
1336 # return list to caller 1337 return self::$UserInterfaceListCache[$FilterExp];
1350 if (!isset(self::$UserInterfacePathsCache[$FilterExp]))
1352 # extract possible UI directories from interface directory list 1353 $InterfaceDirs = array();
1354 foreach ($this->ExpandDirectoryList($this->InterfaceDirList) as $Dir)
1357 if (preg_match(
"#([a-zA-Z0-9/]*interface)/[a-zA-Z0-9%/]*#",
1361 if (!in_array($Dir, $InterfaceDirs))
1363 $InterfaceDirs[] = $Dir;
1368 # reverse order of interface directories so that the directory 1369 # returned is the base directory for the interface 1370 $InterfaceDirs = array_reverse($InterfaceDirs);
1372 # start out with an empty list 1373 self::$UserInterfacePathsCache[$FilterExp] = array();
1374 $InterfacesFound = array();
1376 # for each possible UI directory 1377 foreach ($InterfaceDirs as $InterfaceDir)
1379 # check if the dir exists 1380 if (!is_dir($InterfaceDir))
1385 $Dir = dir($InterfaceDir);
1387 # for each file in current directory 1388 while (($DirEntry = $Dir->read()) !== FALSE)
1390 $InterfacePath = $InterfaceDir.
"/".$DirEntry;
1392 # skip anything we have already found 1393 # or that doesn't have a name in the required format 1394 # or that isn't a directory 1395 # or that doesn't match the filter regex (if supplied) 1396 if (in_array($DirEntry, $InterfacesFound)
1397 || !preg_match(
'/^[a-zA-Z0-9]+$/', $DirEntry)
1398 || !is_dir($InterfacePath)
1399 || (($FilterExp !== NULL)
1400 && !preg_match($FilterExp, $InterfacePath)))
1405 # add interface to list 1406 self::$UserInterfacePathsCache[$FilterExp][$DirEntry] =
1408 $InterfacesFound[] = $DirEntry;
1415 # return list to caller 1416 return self::$UserInterfacePathsCache[$FilterExp];
1444 &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
1445 &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
1446 &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
1448 $FuncIndex = count($this->PostProcessingFuncs);
1449 $this->PostProcessingFuncs[$FuncIndex][
"FunctionName"] = $FunctionName;
1450 $this->PostProcessingFuncs[$FuncIndex][
"Arguments"] = array();
1452 while (isset(${
"Arg".$Index}) && (${
"Arg".$Index} !== self::NOVALUE))
1454 $this->PostProcessingFuncs[$FuncIndex][
"Arguments"][$Index]
1467 $this->EnvIncludes[] = $FileName;
1488 if (($NewSetting === TRUE)
1489 || ($NewSetting === FALSE)
1490 || is_array($NewSetting))
1492 $this->ContextFilters[$Context] = $NewSetting;
1494 elseif (is_string($NewSetting))
1496 $this->ContextFilters[$Context] = array($NewSetting);
1500 throw new InvalidArgumentException(
1501 "Invalid setting (".$NewSetting.
").");
1525 # determine which location to search based on file suffix 1527 $DirList = ($FileType == self::FT_IMAGE)
1528 ? $this->ImageDirList : $this->IncludeDirList;
1530 # if directed to use minimized JavaScript file 1533 # look for minimized version of file 1534 $MinimizedFileName = substr_replace($FileName,
".min", -3, 0);
1535 $FoundFileName = $this->FindFile($DirList, $MinimizedFileName);
1537 # if minimized file was not found 1538 if (is_null($FoundFileName))
1540 # look for unminimized file 1541 $FoundFileName = $this->FindFile($DirList, $FileName);
1543 # if unminimized file found 1544 if (!is_null($FoundFileName))
1546 # if minimization enabled and supported 1548 && self::JsMinRewriteSupport())
1550 # attempt to create minimized file 1551 $MinFileName = $this->MinimizeJavascriptFile(
1554 # if minimization succeeded 1555 if ($MinFileName !== NULL)
1557 # use minimized version 1558 $FoundFileName = $MinFileName;
1560 # save file modification time if needed for fingerprinting 1563 $FileMTime = filemtime($FoundFileName);
1566 # strip off the cache location, allowing .htaccess 1567 # to handle that for us 1568 $FoundFileName = str_replace(
1569 self::$JSMinCacheDir.
"/",
"", $FoundFileName);
1575 # else if directed to use SCSS files 1578 # look for SCSS version of file 1579 $SourceFileName = preg_replace(
"/.css$/",
".scss", $FileName);
1580 $FoundSourceFileName = $this->FindFile($DirList, $SourceFileName);
1582 # if SCSS file not found 1583 if ($FoundSourceFileName === NULL)
1586 $FoundFileName = $this->FindFile($DirList, $FileName);
1590 # compile SCSS file (if updated) and return resulting CSS file 1591 $FoundFileName = $this->CompileScssFile($FoundSourceFileName);
1593 # save file modification time if needed for fingerprinting 1596 $FileMTime = filemtime($FoundFileName);
1599 # strip off the cache location, allowing .htaccess to handle that for us 1600 if (self::ScssRewriteSupport())
1602 $FoundFileName = str_replace(
1603 self::$ScssCacheDir.
"/",
"", $FoundFileName);
1607 # otherwise just search for the file 1610 $FoundFileName = $this->FindFile($DirList, $FileName);
1613 # add non-image files to list of found files (used for required files loading) 1614 if ($FileType != self::FT_IMAGE)
1615 { $this->FoundUIFiles[] = basename($FoundFileName); }
1617 # if UI file fingerprinting is enabled and supported 1619 && self::UrlFingerprintingRewriteSupport()
1620 && (isset($FileMTime) || file_exists($FoundFileName)))
1622 # if file does not appear to be a server-side inclusion 1623 if (!preg_match(
'/\.(html|php)$/i', $FoundFileName))
1625 # for each URL fingerprinting blacklist entry 1626 $OnBlacklist = FALSE;
1627 foreach ($this->UrlFingerprintBlacklist as $BlacklistEntry)
1629 # if entry looks like a regular expression pattern 1630 if ($BlacklistEntry[0] == substr($BlacklistEntry, -1))
1632 # check file name against regular expression 1633 if (preg_match($BlacklistEntry, $FoundFileName))
1635 $OnBlacklist = TRUE;
1641 # check file name directly against entry 1642 if (basename($FoundFileName) == $BlacklistEntry)
1644 $OnBlacklist = TRUE;
1650 # if file was not on blacklist 1653 # get file modification time if not already retrieved 1654 if (!isset($FileMTime))
1656 $FileMTime = filemtime($FoundFileName);
1659 # add timestamp fingerprint to file name 1660 $Fingerprint = sprintf(
"%06X",
1661 ($FileMTime % 0xFFFFFF));
1662 $FoundFileName = preg_replace(
"/^(.+)\.([a-z]+)$/",
1663 "$1.".$Fingerprint.
".$2",
1669 # return file name to caller 1670 return $FoundFileName;
1683 $FullFileName = $this->
GUIFile($FileName);
1684 if ($FullFileName) { print($FullFileName); }
1703 # convert file name to array if necessary 1704 if (!is_array($FileNames)) { $FileNames = [ $FileNames ]; }
1706 # pad additional attributes if supplied 1707 $AddAttribs = $AdditionalAttributes ?
" ".$AdditionalAttributes :
"";
1710 foreach ($FileNames as $BaseFileName)
1712 # retrieve full file name 1713 $FileName = $this->
GUIFile($BaseFileName);
1718 # print appropriate tag 1719 print $this->GetUIFileLoadingTag($FileName, $AdditionalAttributes);
1722 # if we are not already loading an override file 1723 if (!preg_match(
"/-Override.(css|scss|js)$/", $BaseFileName))
1725 # attempt to load override file if available 1730 $OverrideFileName = preg_replace(
1731 "/\.(css|scss)$/",
"-Override.$1",
1734 $AdditionalAttributes);
1737 case self::FT_JAVASCRIPT:
1738 $OverrideFileName = preg_replace(
1739 "/\.js$/",
"-Override.js",
1742 $AdditionalAttributes);
1757 $this->UrlFingerprintBlacklist[] = $Pattern;
1773 # convert file names to array if necessary 1774 if (!is_array($FileNames)) { $FileNames = [ $FileNames ]; }
1776 # add file names to list of required files 1777 foreach ($FileNames as $FileName)
1779 $this->AdditionalRequiredUIFiles[$FileName] = $Order;
1790 static $FileTypeCache;
1791 if (isset($FileTypeCache[$FileName]))
1793 return $FileTypeCache[$FileName];
1796 $FileSuffix = strtolower(substr($FileName, -3));
1797 if ($FileSuffix ==
"css")
1799 $FileTypeCache[$FileName] = self::FT_CSS;
1801 elseif ($FileSuffix ==
".js")
1803 $FileTypeCache[$FileName] = self::FT_JAVASCRIPT;
1805 elseif (($FileSuffix ==
"gif")
1806 || ($FileSuffix ==
"jpg")
1807 || ($FileSuffix ==
"png")
1808 || ($FileSuffix ==
"svg")
1809 || ($FileSuffix ==
"ico"))
1811 $FileTypeCache[$FileName] = self::FT_IMAGE;
1815 $FileTypeCache[$FileName] = self::FT_OTHER;
1818 return $FileTypeCache[$FileName];
1839 # if specified function is not currently available 1840 if (!is_callable($Callback))
1842 # if function info looks legal 1843 if (is_string($Callback) && strlen($Callback))
1845 # start with function directory list 1846 $Locations = $this->FunctionDirList;
1848 # add object directories to list 1849 $Locations = array_merge(
1850 $Locations, array_keys(self::$ObjectDirectories));
1852 # look for function file 1853 $FunctionFileName = $this->FindFile($Locations,
"F-".$Callback,
1854 array(
"php",
"html"));
1856 # if function file was found 1857 if ($FunctionFileName)
1859 # load function file 1860 include_once($FunctionFileName);
1864 # log error indicating function load failed 1865 $this->
LogError(self::LOGLVL_ERROR,
"Unable to load function" 1866 .
" for callback \"".$Callback.
"\".");
1871 # log error indicating specified function info was bad 1872 $this->
LogError(self::LOGLVL_ERROR,
"Unloadable callback value" 1874 .
" passed to AF::LoadFunction() by " 1875 .StdLib::GetMyCaller().
".");
1879 # report to caller whether function load succeeded 1880 return is_callable($Callback);
1889 return microtime(TRUE) - $this->ExecutionStartTime;
1907 # add new meta tag to list 1908 $this->MetaTags[] = $Attribs;
1921 # add new meta tag to list 1922 $this->UniqueMetaTags[] = [
1923 "Attribs" => $Attribs,
1924 "UniqueAttribs" => $UniqueAttribs,
1931 # ---- Page Caching ------------------------------------------------------ 1947 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
1962 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
1971 $this->CacheCurrentPage = FALSE;
1983 $Tag = strtolower($Tag);
1985 # if pages were supplied 1986 if ($Pages !== NULL)
1988 # add pages to list for this tag 1989 if (isset($this->PageCacheTags[$Tag]))
1991 $this->PageCacheTags[$Tag] = array_merge(
1992 $this->PageCacheTags[$Tag], $Pages);
1996 $this->PageCacheTags[$Tag] = $Pages;
2001 # add current page to list for this tag 2002 $this->PageCacheTags[$Tag][] =
"CURRENT";
2014 $TagId = $this->GetPageCacheTagId($Tag);
2016 # delete pages and tag/page connections for specified tag 2017 $this->DB->Query(
"DELETE CP, CPTI" 2018 .
" FROM AF_CachedPages CP, AF_CachedPageTagInts CPTI" 2019 .
" WHERE CPTI.TagId = ".intval($TagId)
2020 .
" AND CP.CacheId = CPTI.CacheId");
2028 # clear all page cache tables 2029 $this->DB->Query(
"TRUNCATE TABLE AF_CachedPages");
2030 $this->DB->Query(
"TRUNCATE TABLE AF_CachedPageTags");
2031 $this->DB->Query(
"TRUNCATE TABLE AF_CachedPageTagInts");
2042 $Length = $this->DB->Query(
"SELECT COUNT(*) AS CacheLen" 2043 .
" FROM AF_CachedPages",
"CacheLen");
2044 $Oldest = $this->DB->Query(
"SELECT CachedAt FROM AF_CachedPages" 2045 .
" ORDER BY CachedAt ASC LIMIT 1",
"CachedAt");
2047 "NumberOfEntries" => $Length,
2048 "OldestTimestamp" => strtotime($Oldest),
2055 # ---- Logging ----------------------------------------------------------- 2075 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2091 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2110 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2127 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2145 # if error level is at or below current logging level 2146 if ($this->Settings[
"LoggingLevel"] >= $Level)
2148 # attempt to log error message 2151 # if logging attempt failed and level indicated significant error 2152 if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
2154 # throw exception about inability to log error 2155 static $AlreadyThrewException = FALSE;
2156 if (!$AlreadyThrewException)
2158 $AlreadyThrewException = TRUE;
2159 throw new Exception(
"Unable to log error (".$Level.
": ".$Msg
2160 .
") to ".$this->LogFileName);
2164 # report to caller whether message was logged 2169 # report to caller that message was not logged 2187 # if message level is at or below current logging level 2188 if ($this->Settings[
"LoggingLevel"] >= $Level)
2190 # attempt to open log file 2191 $FHndl = @fopen($this->LogFileName,
"a");
2193 # if log file could not be open 2194 if ($FHndl === FALSE)
2196 # report to caller that message was not logged 2202 $ErrorAbbrevs = array(
2203 self::LOGLVL_FATAL =>
"FTL",
2204 self::LOGLVL_ERROR =>
"ERR",
2205 self::LOGLVL_WARNING =>
"WRN",
2206 self::LOGLVL_INFO =>
"INF",
2207 self::LOGLVL_DEBUG =>
"DBG",
2208 self::LOGLVL_TRACE =>
"TRC",
2210 $Msg = str_replace(array(
"\n",
"\t",
"\r"),
" ", $Msg);
2211 $Msg = substr(trim($Msg), 0, self::LOGFILE_MAX_LINE_LENGTH);
2212 $LogEntry = date(
"Y-m-d H:i:s")
2213 .
" ".($this->RunningInBackground ?
"B" :
"F")
2214 .
" ".$ErrorAbbrevs[$Level]
2217 # write entry to log 2218 $Success = fwrite($FHndl, $LogEntry.
"\n");
2223 # report to caller whether message was logged 2224 return ($Success === FALSE) ? FALSE : TRUE;
2229 # report to caller that message was not logged 2260 # constrain new level (if supplied) to within legal bounds 2263 $NewValue = max(min($NewValue, 6), 1);
2266 # set new logging level (if supplied) and return current level to caller 2267 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2278 if ($NewValue !== NULL) { $this->LogFileName = $NewValue; }
2279 return $this->LogFileName;
2293 # return no entries if there isn't a log file 2294 # or we can't read it or it's empty 2296 if (!is_readable($LogFile) || !filesize($LogFile))
2301 # if max number of entries specified 2304 # load lines from file 2305 $FHandle = fopen($LogFile,
"r");
2306 $FileSize = filesize($LogFile);
2307 $SeekPosition = max(0,
2308 ($FileSize - (self::LOGFILE_MAX_LINE_LENGTH
2310 fseek($FHandle, $SeekPosition);
2311 $Block = fread($FHandle, ($FileSize - $SeekPosition));
2313 $Lines = explode(PHP_EOL, $Block);
2316 # prune array back to requested number of entries 2317 $Lines = array_slice($Lines, (0 - $Limit));
2321 # load all lines from log file 2322 $Lines = file($LogFile, FILE_IGNORE_NEW_LINES);
2323 if ($Lines === FALSE)
2329 # reverse line order 2330 $Lines = array_reverse($Lines);
2332 # for each log file line 2334 foreach ($Lines as $Line)
2336 # attempt to parse line into component parts 2337 $Pieces = explode(
" ", $Line, 5);
2338 $Date = isset($Pieces[0]) ? $Pieces[0] :
"";
2339 $Time = isset($Pieces[1]) ? $Pieces[1] :
"";
2340 $Back = isset($Pieces[2]) ? $Pieces[2] :
"";
2341 $Level = isset($Pieces[3]) ? $Pieces[3] :
"";
2342 $Msg = isset($Pieces[4]) ? $Pieces[4] :
"";
2344 # skip line if it looks invalid 2345 $ErrorAbbrevs = array(
2346 "FTL" => self::LOGLVL_FATAL,
2347 "ERR" => self::LOGLVL_ERROR,
2348 "WRN" => self::LOGLVL_WARNING,
2349 "INF" => self::LOGLVL_INFO,
2350 "DBG" => self::LOGLVL_DEBUG,
2351 "TRC" => self::LOGLVL_TRACE,
2353 if ((($Back !=
"F") && ($Back !=
"B"))
2354 || !array_key_exists($Level, $ErrorAbbrevs)
2360 # convert parts into appropriate values and add to entries 2362 "Time" => strtotime($Date.
" ".$Time),
2363 "Background" => ($Back ==
"B") ? TRUE : FALSE,
2364 "Level" => $ErrorAbbrevs[$Level],
2369 # return entries to caller 2414 # ---- Event Handling ---------------------------------------------------- 2460 # convert parameters to array if not already in that form 2461 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2462 : array($EventsOrEventName => $EventType);
2465 foreach ($Events as $Name => $Type)
2467 # store event information 2468 $this->RegisteredEvents[$Name][
"Type"] = $Type;
2469 $this->RegisteredEvents[$Name][
"Hooks"] = array();
2481 return array_key_exists($EventName, $this->RegisteredEvents)
2493 # the event isn't hooked to if it isn't even registered 2499 # return TRUE if there is at least one callback hooked to the event 2500 return count($this->RegisteredEvents[$EventName][
"Hooks"]) > 0;
2517 $EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
2519 # convert parameters to array if not already in that form 2520 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2521 : array($EventsOrEventName => $Callback);
2525 foreach ($Events as $EventName => $EventCallback)
2527 # if callback is valid 2528 if (is_callable($EventCallback))
2530 # if this is a periodic event we process internally 2531 if (isset($this->PeriodicEvents[$EventName]))
2534 $this->ProcessPeriodicEvent($EventName, $EventCallback);
2536 # if specified event has been registered 2537 elseif (isset($this->RegisteredEvents[$EventName]))
2539 # add callback for event 2540 $this->RegisteredEvents[$EventName][
"Hooks"][]
2541 = array(
"Callback" => $EventCallback,
"Order" => $Order);
2543 # sort callbacks by order 2544 if (count($this->RegisteredEvents[$EventName][
"Hooks"]) > 1)
2546 usort($this->RegisteredEvents[$EventName][
"Hooks"],
2549 $A[
"Order"], $B[
"Order"]);
2564 # report to caller whether all callbacks were hooked 2582 $EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
2584 # convert parameters to array if not already in that form 2585 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2586 : array($EventsOrEventName => $Callback);
2590 foreach ($Events as $EventName => $EventCallback)
2592 # if this event has been registered and hooked 2593 if (isset($this->RegisteredEvents[$EventName])
2594 && count($this->RegisteredEvents[$EventName]))
2596 # if this callback has been hooked for this event 2597 $CallbackData = array(
"Callback" => $EventCallback,
"Order" => $Order);
2598 if (in_array($CallbackData,
2599 $this->RegisteredEvents[$EventName][
"Hooks"]))
2602 $HookIndex = array_search($CallbackData,
2603 $this->RegisteredEvents[$EventName][
"Hooks"]);
2604 unset($this->RegisteredEvents[$EventName][
"Hooks"][$HookIndex]);
2610 # report number of callbacks unhooked to caller 2611 return $UnhookCount;
2626 $ReturnValue = NULL;
2628 # if event has been registered 2629 if (isset($this->RegisteredEvents[$EventName]))
2631 # set up default return value (if not NULL) 2632 switch ($this->RegisteredEvents[$EventName][
"Type"])
2634 case self::EVENTTYPE_CHAIN:
2635 $ReturnValue = $Parameters;
2638 case self::EVENTTYPE_NAMED:
2639 $ReturnValue = array();
2643 # for each callback for this event 2644 foreach ($this->RegisteredEvents[$EventName][
"Hooks"] as $Hook)
2647 $Callback = $Hook[
"Callback"];
2648 $Result = ($Parameters !== NULL)
2649 ? call_user_func_array($Callback, $Parameters)
2650 : call_user_func($Callback);
2652 # process return value based on event type 2653 switch ($this->RegisteredEvents[$EventName][
"Type"])
2655 case self::EVENTTYPE_CHAIN:
2656 if ($Result !== NULL)
2658 foreach ($Parameters as $Index => $Value)
2660 if (array_key_exists($Index, $Result))
2662 $Parameters[$Index] = $Result[$Index];
2665 $ReturnValue = $Parameters;
2669 case self::EVENTTYPE_FIRST:
2670 if ($Result !== NULL)
2672 $ReturnValue = $Result;
2677 case self::EVENTTYPE_NAMED:
2678 $CallbackName = is_array($Callback)
2679 ? (is_object($Callback[0])
2680 ? get_class($Callback[0])
2681 : $Callback[0]).
"::".$Callback[1]
2683 $ReturnValue[$CallbackName] = $Result;
2693 $this->
LogError(self::LOGLVL_WARNING,
2694 "Unregistered event (".$EventName.
") signaled by " 2695 .StdLib::GetMyCaller().
".");
2698 # return value if any to caller 2699 return $ReturnValue;
2709 return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
2724 # if event is not a periodic event report failure to caller 2725 if (!array_key_exists($EventName, $this->EventPeriods)) {
return FALSE; }
2727 # retrieve last execution time for event if available 2728 $Signature = self::GetCallbackSignature($Callback);
2729 $LastRunTime = $this->DB->Query(
"SELECT LastRunAt FROM PeriodicEvents" 2730 .
" WHERE Signature = '".addslashes($Signature).
"'",
"LastRunAt");
2732 # if event was not found report failure to caller 2733 if ($LastRunTime === NULL) {
return FALSE; }
2735 # calculate next run time based on event period 2736 $NextRunTime = strtotime($LastRunTime) + $this->EventPeriods[$EventName];
2738 # report next run time to caller 2739 return $NextRunTime;
2759 # retrieve last execution times 2760 $this->DB->Query(
"SELECT * FROM PeriodicEvents");
2761 $LastRunTimes = $this->DB->FetchColumn(
"LastRunAt",
"Signature");
2763 # for each known event 2765 foreach ($this->KnownPeriodicEvents as $Signature => $Info)
2767 # if last run time for event is available 2768 if (array_key_exists($Signature, $LastRunTimes))
2770 # calculate next run time for event 2771 $LastRun = strtotime($LastRunTimes[$Signature]);
2772 $NextRun = $LastRun + $this->EventPeriods[$Info[
"Period"]];
2773 if ($Info[
"Period"] ==
"EVENT_PERIODIC") { $LastRun = FALSE; }
2777 # set info to indicate run times are not known 2782 # add event info to list 2783 $Events[$Signature] = $Info;
2784 $Events[$Signature][
"LastRun"] = $LastRun;
2785 $Events[$Signature][
"NextRun"] = $NextRun;
2786 $Events[$Signature][
"Parameters"] = NULL;
2789 # return list of known events to caller 2802 if (!isset($DB)) { $DB =
new Database(); }
2805 $ReturnVal = call_user_func_array($Callback, $Parameters);
2807 # if event is already in database 2808 $Signature = self::GetCallbackSignature($Callback);
2809 if ($DB->Query(
"SELECT COUNT(*) AS EventCount FROM PeriodicEvents" 2810 .
" WHERE Signature = '".addslashes($Signature).
"'",
"EventCount"))
2812 # update last run time for event 2813 $DB->Query(
"UPDATE PeriodicEvents SET LastRunAt = " 2814 .(($EventName ==
"EVENT_PERIODIC")
2815 ?
"'".date(
"Y-m-d H:i:s", time() + ($ReturnVal * 60)).
"'" 2817 .
" WHERE Signature = '".addslashes($Signature).
"'");
2821 # add last run time for event to database 2822 $DB->Query(
"INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES " 2823 .
"('".addslashes($Signature).
"', " 2824 .(($EventName ==
"EVENT_PERIODIC")
2825 ?
"'".date(
"Y-m-d H:i:s", time() + ($ReturnVal * 60)).
"'" 2833 # ---- Task Management --------------------------------------------------- 2859 $Priority = self::PRIORITY_LOW, $Description =
"")
2861 # pack task info and write to database 2862 if ($Parameters === NULL) { $Parameters = array(); }
2863 $this->DB->Query(
"INSERT INTO TaskQueue" 2864 .
" (Callback, Parameters, Priority, Description)" 2865 .
" VALUES ('".addslashes(serialize($Callback)).
"', '" 2866 .addslashes(serialize($Parameters)).
"', ".intval($Priority).
", '" 2867 .addslashes($Description).
"')");
2888 $Priority = self::PRIORITY_LOW, $Description =
"")
2892 $QueryResult = $this->DB->Query(
"SELECT TaskId,Priority FROM TaskQueue" 2893 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'" 2894 .($Parameters ?
" AND Parameters = '" 2895 .addslashes(serialize($Parameters)).
"'" :
""));
2896 if ($QueryResult !== FALSE)
2898 $Record = $this->DB->FetchRow();
2899 if ($Record[
"Priority"] > $Priority)
2901 $this->DB->Query(
"UPDATE TaskQueue" 2902 .
" SET Priority = ".intval($Priority)
2903 .
" WHERE TaskId = ".intval($Record[
"TaskId"]));
2910 $this->
QueueTask($Callback, $Parameters, $Priority, $Description);
2926 $QueuedCount = $this->DB->Query(
2927 "SELECT COUNT(*) AS FoundCount FROM TaskQueue" 2928 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'" 2929 .($Parameters ?
" AND Parameters = '" 2930 .addslashes(serialize($Parameters)).
"'" :
""),
2932 $RunningCount = $this->DB->Query(
2933 "SELECT COUNT(*) AS FoundCount FROM RunningTasks" 2934 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'" 2935 .($Parameters ?
" AND Parameters = '" 2936 .addslashes(serialize($Parameters)).
"'" :
""),
2938 $FoundCount = $QueuedCount + $RunningCount;
2939 return ($FoundCount ? TRUE : FALSE);
2961 return $this->GetTaskList(
"SELECT * FROM TaskQueue" 2962 .
" ORDER BY Priority, TaskId ", $Count, $Offset);
2979 $Parameters = NULL, $Priority = NULL, $Description = NULL)
2981 $Query =
"SELECT COUNT(*) AS TaskCount FROM TaskQueue";
2983 if ($Callback !== NULL)
2985 $Query .= $Sep.
" Callback = '".addslashes(serialize($Callback)).
"'";
2988 if ($Parameters !== NULL)
2990 $Query .= $Sep.
" Parameters = '".addslashes(serialize($Parameters)).
"'";
2993 if ($Priority !== NULL)
2995 $Query .= $Sep.
" Priority = ".intval($Priority);
2998 if ($Description !== NULL)
3000 $Query .= $Sep.
" Description = '".addslashes($Description).
"'";
3002 return $this->DB->Query($Query,
"TaskCount");
3014 return $this->GetTaskList(
"SELECT * FROM RunningTasks" 3015 .
" WHERE StartedAt >= '".date(
"Y-m-d H:i:s",
3017 .
" ORDER BY StartedAt", $Count, $Offset);
3029 return $this->GetTaskList(
"SELECT * FROM RunningTasks" 3030 .
" WHERE StartedAt < '".date(
"Y-m-d H:i:s",
3032 .
" ORDER BY StartedAt", $Count, $Offset);
3041 return $this->DB->Query(
"SELECT COUNT(*) AS Count FROM RunningTasks" 3042 .
" WHERE StartedAt < '".date(
"Y-m-d H:i:s",
3054 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
3055 $this->DB->Query(
"INSERT INTO TaskQueue" 3056 .
" (Callback,Parameters,Priority,Description) " 3057 .
"SELECT Callback, Parameters, Priority, Description" 3058 .
" FROM RunningTasks WHERE TaskId = ".intval($TaskId));
3059 if ($NewPriority !== NULL)
3061 $NewTaskId = $this->DB->LastInsertId();
3062 $this->DB->Query(
"UPDATE TaskQueue SET Priority = " 3063 .intval($NewPriority)
3064 .
" WHERE TaskId = ".intval($NewTaskId));
3066 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
3067 $this->DB->Query(
"UNLOCK TABLES");
3088 $this->DB->Query(
"DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
3089 $TasksRemoved = $this->DB->NumRowsAffected();
3090 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
3091 $TasksRemoved += $this->DB->NumRowsAffected();
3092 return $TasksRemoved;
3104 # assume task will not be found 3107 # look for task in task queue 3108 $this->DB->Query(
"SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
3110 # if task was not found in queue 3111 if (!$this->DB->NumRowsSelected())
3113 # look for task in running task list 3114 $this->DB->Query(
"SELECT * FROM RunningTasks WHERE TaskId = " 3119 if ($this->DB->NumRowsSelected())
3121 # if task was periodic 3122 $Row = $this->DB->FetchRow();
3123 if ($Row[
"Callback"] ==
3124 serialize(array(
"ApplicationFramework",
"RunPeriodicEvent")))
3126 # unpack periodic task callback 3127 $WrappedCallback = unserialize($Row[
"Parameters"]);
3128 $Task[
"Callback"] = $WrappedCallback[1];
3129 $Task[
"Parameters"] = $WrappedCallback[2];
3133 # unpack task callback and parameters 3134 $Task[
"Callback"] = unserialize($Row[
"Callback"]);
3135 $Task[
"Parameters"] = unserialize($Row[
"Parameters"]);
3139 # return task to caller 3155 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
3168 return $this->UpdateSetting(
"MaxTasksRunning", $NewValue, $Persistent);
3180 # if task callback is function use function name 3181 $Callback = $TaskInfo[
"Callback"];
3183 if (!is_array($Callback))
3189 # if task callback is object 3190 if (is_object($Callback[0]))
3192 # if task callback is encapsulated ask encapsulation for name 3193 if (method_exists($Callback[0],
"GetCallbackAsText"))
3195 $Name = $Callback[0]->GetCallbackAsText();
3197 # else assemble name from object 3200 $Name = get_class($Callback[0]) .
"::" . $Callback[1];
3203 # else assemble name from supplied info 3206 $Name= $Callback[0] .
"::" . $Callback[1];
3210 # if parameter array was supplied 3211 $Parameters = $TaskInfo[
"Parameters"];
3212 $ParameterString =
"";
3213 if (is_array($Parameters))
3215 # assemble parameter string 3217 foreach ($Parameters as $Parameter)
3219 $ParameterString .= $Separator;
3220 if (is_int($Parameter) || is_float($Parameter))
3222 $ParameterString .= $Parameter;
3224 else if (is_string($Parameter))
3226 $ParameterString .=
"\"".htmlspecialchars($Parameter).
"\"";
3228 else if (is_array($Parameter))
3230 $ParameterString .=
"ARRAY";
3232 else if (is_object($Parameter))
3234 $ParameterString .=
"OBJECT";
3236 else if (is_null($Parameter))
3238 $ParameterString .=
"NULL";
3240 else if (is_bool($Parameter))
3242 $ParameterString .= $Parameter ?
"TRUE" :
"FALSE";
3244 else if (is_resource($Parameter))
3246 $ParameterString .= get_resource_type($Parameter);
3250 $ParameterString .=
"????";
3256 # assemble name and parameters and return result to caller 3257 return $Name.
"(".$ParameterString.
")";
3266 return $this->RunningInBackground;
3276 return isset($this->RunningTask)
3277 ? $this->RunningTask[
"Priority"] : NULL;
3290 if ($Priority === NULL)
3293 if ($Priority === NULL)
3298 return ($Priority > self::PRIORITY_HIGH)
3299 ? ($Priority - 1) : self::PRIORITY_HIGH;
3312 if ($Priority === NULL)
3315 if ($Priority === NULL)
3320 return ($Priority < self::PRIORITY_BACKGROUND)
3321 ? ($Priority + 1) : self::PRIORITY_BACKGROUND;
3327 # ---- Clean URL Support ------------------------------------------------- 3357 public function AddCleanUrl($Pattern, $Page, $GetVars = NULL, $Template = NULL)
3359 # save clean URL mapping parameters 3360 $this->CleanUrlMappings[] = array(
3361 "Pattern" => $Pattern,
3363 "GetVars" => $GetVars,
3367 # if replacement template specified 3368 if ($Template !== NULL)
3370 # if GET parameters specified 3371 if (count($GetVars))
3373 # retrieve all possible permutations of GET parameters 3376 # for each permutation of GET parameters 3377 foreach ($GetPerms as $VarPermutation)
3379 # construct search pattern for permutation 3380 $SearchPattern =
"/href=([\"'])index\\.php\\?P=".$Page;
3381 $GetVarSegment =
"";
3382 foreach ($VarPermutation as $GetVar)
3384 if (preg_match(
"%\\\$[0-9]+%", $GetVars[$GetVar]))
3386 $GetVarSegment .=
"&".$GetVar.
"=((?:(?!\\1)[^&])+)";
3390 $GetVarSegment .=
"&".$GetVar.
"=".$GetVars[$GetVar];
3393 $SearchPattern .= $GetVarSegment.
"\\1/i";
3395 # if template is actually a callback 3396 if (is_callable($Template))
3398 # add pattern to HTML output mod callbacks list 3399 $this->OutputModificationCallbacks[] = array(
3400 "Pattern" => $Pattern,
3402 "SearchPattern" => $SearchPattern,
3403 "Callback" => $Template,
3408 # construct replacement string for permutation 3409 $Replacement = $Template;
3411 foreach ($VarPermutation as $GetVar)
3413 $Replacement = str_replace(
3414 "\$".$GetVar,
"\$".$Index, $Replacement);
3417 $Replacement =
"href=\"".$Replacement.
"\"";
3419 # add pattern to HTML output modifications list 3420 $this->OutputModificationPatterns[] = $SearchPattern;
3421 $this->OutputModificationReplacements[] = $Replacement;
3427 # construct search pattern 3428 $SearchPattern =
"/href=\"index\\.php\\?P=".$Page.
"\"/i";
3430 # if template is actually a callback 3431 if (is_callable($Template))
3433 # add pattern to HTML output mod callbacks list 3434 $this->OutputModificationCallbacks[] = array(
3435 "Pattern" => $Pattern,
3437 "SearchPattern" => $SearchPattern,
3438 "Callback" => $Template,
3443 # add simple pattern to HTML output modifications list 3444 $this->OutputModificationPatterns[] = $SearchPattern;
3445 $this->OutputModificationReplacements[] =
"href=\"".$Template.
"\"";
3458 foreach ($this->CleanUrlMappings as $Info)
3460 if (preg_match($Info[
"Pattern"], $Path))
3479 # the search patterns and callbacks require a specific format 3480 $Format =
"href=\"".str_replace(
"&",
"&", $Path).
"\"";
3483 # perform any regular expression replacements on the search string 3484 $Search = preg_replace($this->OutputModificationPatterns,
3485 $this->OutputModificationReplacements, $Search);
3487 # only run the callbacks if a replacement hasn't already been performed 3488 if ($Search == $Format)
3490 # perform any callback replacements on the search string 3491 foreach ($this->OutputModificationCallbacks as $Info)
3493 # make the information available to the callback 3494 $this->OutputModificationCallbackInfo = $Info;
3496 # execute the callback 3497 $Search = preg_replace_callback($Info[
"SearchPattern"],
3498 array($this,
"OutputModificationCallbackShell"),
3503 # return the path untouched if no replacements were performed 3504 if ($Search == $Format)
3509 # remove the bits added to the search string to get it recognized by 3510 # the replacement expressions and callbacks 3511 $Result = substr($Search, 6, -1);
3524 # for each clean URL mapping 3525 foreach ($this->CleanUrlMappings as $Info)
3527 # if current path matches the clean URL pattern 3528 if (preg_match($Info[
"Pattern"], $Path, $Matches))
3530 # the GET parameters for the URL, starting with the page name 3531 $GetVars = array(
"P" => $Info[
"Page"]);
3533 # if additional $_GET variables specified for clean URL 3534 if ($Info[
"GetVars"] !== NULL)
3536 # for each $_GET variable specified for clean URL 3537 foreach ($Info[
"GetVars"] as $VarName => $VarTemplate)
3539 # start with template for variable value 3540 $Value = $VarTemplate;
3542 # for each subpattern matched in current URL 3543 foreach ($Matches as $Index => $Match)
3545 # if not first (whole) match 3548 # make any substitutions in template 3549 $Value = str_replace(
"$".$Index, $Match, $Value);
3553 # add the GET variable 3554 $GetVars[$VarName] = $Value;
3558 # return the unclean URL 3559 return "index.php?" . http_build_query($GetVars);
3563 # return the path unchanged 3583 $GetVars = array(
"P" => $this->
GetPageName()) + $_GET;
3584 return "index.php?" . http_build_query($GetVars);
3596 return $this->CleanUrlMappings;
3613 $this->AlternateDomainPrefixes[$Domain] = $Prefix;
3623 return array_keys($this->AlternateDomainPrefixes);
3634 return isset($this->AlternateDomainPrefixes[$Domain]) ?
3635 $this->AlternateDomainPrefixes[$Domain] : NULL;
3640 # ---- Server Environment ------------------------------------------------ 3651 if ($NewValue !== NULL)
3653 self::$SessionLifetime = $NewValue;
3655 return self::$SessionLifetime;
3665 return isset($_SERVER[
"HTACCESS_SUPPORT"])
3666 || isset($_SERVER[
"REDIRECT_HTACCESS_SUPPORT"]);
3677 return isset($_SERVER[
"URL_FINGERPRINTING_SUPPORT"])
3678 || isset($_SERVER[
"REDIRECT_URL_FINGERPRINTING_SUPPORT"]);
3689 return isset($_SERVER[
"SCSS_REWRITE_SUPPORT"])
3690 || isset($_SERVER[
"REDIRECT_SCSS_REWRITE_SUPPORT"]);
3701 return isset($_SERVER[
"JSMIN_REWRITE_SUPPORT"])
3702 || isset($_SERVER[
"REDIRECT_JSMIN_REWRITE_SUPPORT"]);
3714 # return override value if one is set 3715 if (self::$RootUrlOverride !== NULL)
3717 return self::$RootUrlOverride;
3720 # determine scheme name 3721 $Protocol = (isset($_SERVER[
"HTTPS"]) ?
"https" :
"http");
3723 # if HTTP_HOST is preferred or SERVER_NAME points to localhost 3724 # and HTTP_HOST is set 3725 if ((self::$PreferHttpHost || ($_SERVER[
"SERVER_NAME"] ==
"127.0.0.1"))
3726 && isset($_SERVER[
"HTTP_HOST"]))
3728 # use HTTP_HOST for domain name 3729 $DomainName = $_SERVER[
"HTTP_HOST"];
3733 # use SERVER_NAME for domain name 3734 $DomainName = $_SERVER[
"SERVER_NAME"];
3737 # build URL root and return to caller 3738 return $Protocol.
"://".$DomainName;
3757 if ($NewValue !== self::NOVALUE)
3759 self::$RootUrlOverride = strlen(trim($NewValue)) ? $NewValue : NULL;
3761 return self::$RootUrlOverride;
3775 $BaseUrl = self::RootUrl().dirname($_SERVER[
"SCRIPT_NAME"]);
3776 if (substr($BaseUrl, -1) !=
"/") { $BaseUrl .=
"/"; }
3789 return self::RootUrl().$_SERVER[
"REQUEST_URI"];
3804 if ($NewValue !== NULL)
3806 self::$PreferHttpHost = ($NewValue ? TRUE : FALSE);
3808 return self::$PreferHttpHost;
3817 $BasePath = dirname($_SERVER[
"SCRIPT_NAME"]);
3819 if (substr($BasePath, -1) !=
"/")
3834 if (array_key_exists(
"SCRIPT_URL", $_SERVER))
3836 return $_SERVER[
"SCRIPT_URL"];
3838 elseif (array_key_exists(
"REQUEST_URI", $_SERVER))
3840 $Pieces = parse_url($_SERVER[
"REQUEST_URI"]);
3841 return isset($Pieces[
"path"]) ? $Pieces[
"path"] : NULL;
3843 elseif (array_key_exists(
"REDIRECT_URL", $_SERVER))
3845 return $_SERVER[
"REDIRECT_URL"];
3863 # needed to get the path of the URL minus the query and fragment pieces 3864 $Components = parse_url(self::GetScriptUrl());
3866 # if parsing was successful and a path is set 3867 if (is_array($Components) && isset($Components[
"path"]))
3869 $BasePath = self::BasePath();
3870 $Path = $Components[
"path"];
3872 # the URL was rewritten if the path isn't the base path, i.e., the 3873 # home page, and the file in the URL isn't the script generating the 3875 if ($BasePath != $Path && basename($Path) != $ScriptName)
3881 # the URL wasn't rewritten 3896 if ($NewSetting !== NULL)
3898 self::$IsAjaxPageLoad = $NewSetting;
3901 if (isset(self::$IsAjaxPageLoad))
3903 return self::$IsAjaxPageLoad;
3905 elseif (isset($_SERVER[
"HTTP_X_REQUESTED_WITH"])
3906 && (strtolower($_SERVER[
"HTTP_X_REQUESTED_WITH"])
3907 ==
"xmlhttprequest"))
3924 return self::GetPhpMemoryLimit() - memory_get_usage(TRUE);
3934 return self::ConvertPhpIniSizeToBytes(
3935 ini_get(
"memory_limit"));
3947 self::ConvertPhpIniSizeToBytes(
3948 ini_get(
"post_max_size")),
3949 self::ConvertPhpIniSizeToBytes(
3950 ini_get(
"upload_max_filesize")));
3969 $NewValue = max($NewValue, 5);
3970 ini_set(
"max_execution_time", $NewValue);
3972 $this->UpdateSetting(
"MaxExecTime", $NewValue, $Persistent);
3974 return ini_get(
"max_execution_time");
3980 # ---- Utility ----------------------------------------------------------- 3995 public function DownloadFile($FilePath, $FileName = NULL, $MimeType = NULL)
3997 # check that file is readable 3998 if (!is_readable($FilePath))
4003 # if file name was not supplied 4004 if ($FileName === NULL)
4006 # extract file name from path 4007 $FileName = basename($FilePath);
4010 # if MIME type was not supplied 4011 if ($MimeType === NULL)
4013 # attempt to determine MIME type 4014 $FInfoHandle = finfo_open(FILEINFO_MIME);
4017 $FInfoMime = finfo_file($FInfoHandle, $FilePath);
4018 finfo_close($FInfoHandle);
4021 $MimeType = $FInfoMime;
4025 # use default if unable to determine MIME type 4026 if ($MimeType === NULL)
4028 $MimeType =
"application/octet-stream";
4032 # list of mime types where we allow the browser to decide on 4033 # how to display the item by omitting the Content-Disposition 4042 # set headers to download file 4043 header(
"Content-Type: ".$MimeType);
4044 header(
"Content-Length: ".filesize($FilePath));
4045 if ($this->CleanUrlRewritePerformed &&
4046 !in_array($MimeType, $InlineTypes))
4048 header(
'Content-Disposition: attachment; filename="'.$FileName.
'"');
4051 # make sure that apache does not attempt to compress file 4052 apache_setenv(
'no-gzip',
'1');
4054 # send file to user, but unbuffered to avoid memory issues 4057 $BlockSize = 512000;
4059 $Handle = @fopen($File,
"rb");
4060 if ($Handle === FALSE)
4065 # (close out session, making it read-only, so that session file 4066 # lock is released and others are not potentially hanging 4067 # waiting for it while the download completes) 4068 session_write_close();
4070 while (!feof($Handle))
4072 print fread($Handle, $BlockSize);
4077 }, array($FilePath));
4079 # prevent HTML output that might interfere with download 4082 # set flag to indicate not to log a slow page load in case client 4083 # connection delays PHP execution because of header 4084 $this->DoNotLogSlowPageLoad = TRUE;
4086 # report no errors found to caller 4104 # assume we will not get a lock 4107 # clear out any stale locks 4108 static $CleanupHasBeenDone = FALSE;
4109 if (!$CleanupHasBeenDone)
4111 # (margin for clearing stale locks is twice the known 4112 # maximum PHP execution time, because the max time 4113 # techinically does not include external operations 4114 # like database queries) 4117 $this->DB->Query(
"DELETE FROM AF_Locks WHERE" 4118 .
" ObtainedAt < '".$ClearLocksObtainedBefore.
"' AND" 4119 .
" LockName = '".addslashes($LockName).
"'");
4124 # lock database table so nobody else can try to get a lock 4125 $this->DB->Query(
"LOCK TABLES AF_Locks WRITE");
4127 # look for lock with specified name 4128 $FoundCount = $this->DB->Query(
"SELECT COUNT(*) AS FoundCount" 4129 .
" FROM AF_Locks WHERE LockName = '" 4130 .addslashes($LockName).
"'",
"FoundCount");
4131 $LockFound = ($FoundCount > 0) ? TRUE : FALSE;
4136 # unlock database tables 4137 $this->DB->Query(
"UNLOCK TABLES");
4139 # if blocking was requested 4142 # wait to give someone else a chance to release lock 4148 # while lock was found and blocking was requested 4149 }
while ($LockFound && $Wait);
4152 # if lock was not found 4156 $this->DB->Query(
"INSERT INTO AF_Locks (LockName) VALUES ('" 4157 .addslashes($LockName).
"')");
4160 # unlock database tables 4161 $this->DB->Query(
"UNLOCK TABLES");
4164 # report to caller whether lock was obtained 4177 # release any existing locks 4178 $this->DB->Query(
"DELETE FROM AF_Locks WHERE LockName = '" 4179 .addslashes($LockName).
"'");
4181 # report to caller whether existing lock was released 4182 return $this->DB->NumRowsAffected() ? TRUE : FALSE;
4205 switch ($ResponseType)
4209 header(
"Content-Type: application/json; charset=" 4210 .$GLOBALS[
"G_SysConfig"]->DefaultCharacterSet(), TRUE);
4214 header(
"Content-Type: application/xml; charset=" 4215 .$GLOBALS[
"G_SysConfig"]->DefaultCharacterSet(), TRUE);
4220 throw new Exception(
4221 "Unsupported response type: ".$ResponseType);
4225 self::$DefaultBrowserCacheExpiration);
4229 session_write_close();
4241 # set headers to control caching 4242 header(
"Expires: ".gmdate(
"D, d M Y H:i:s \G\M\T", time()+$MaxAge));
4243 header(
"Cache-Control: private, max-age=".$MaxAge);
4255 $Str = strtoupper($Size);
4257 # trim off 'B' suffix for KB/MB/GB 4258 if (substr($Str, -1) ==
"B")
4260 $Str = substr($Str, 0, strlen($Str) - 1);
4263 # pull out the numeric part of our setting 4264 $Val = intval($Str);
4266 # adjust it based on the units 4267 switch (substr($Str, -1))
4295 if ($InUse !== NULL)
4300 return $this->SessionInUse;
4306 # ---- Backward Compatibility -------------------------------------------- 4318 return $this->FindFile(
4319 $this->IncludeDirList, $BaseName, array(
"tpl",
"html"));
4325 # ---- PRIVATE INTERFACE ------------------------------------------------- 4327 private $AdditionalRequiredUIFiles = array();
4328 private $AlternateDomainPrefixes = array();
4329 private $BackgroundTaskMemLeakLogThreshold = 10; # percentage of max mem
4330 private $BackgroundTaskMinFreeMemPercent = 25;
4331 private $BrowserDetectFunc;
4332 private $CacheCurrentPage = TRUE;
4333 private $CleanUrlMappings = array();
4334 private $CleanUrlRewritePerformed = FALSE;
4335 private $ContextFilters = array(
4336 self::CONTEXT_START => TRUE,
4337 self::CONTEXT_PAGE => array(
"H_"),
4338 self::CONTEXT_COMMON => array(
"H_"),
4340 private $CssUrlFingerprintPath;
4342 private $DefaultPage =
"Home";
4343 private $DoNotMinimizeList = array();
4344 private $DoNotLogSlowPageLoad = FALSE;
4345 private $EnvIncludes = array();
4346 private $ExecutionStartTime;
4347 private $FoundUIFiles = array();
4348 private $HtmlCharset =
"UTF-8";
4349 private $InterfaceSettings = array();
4350 private $JSMinimizerJavaScriptPackerAvailable = FALSE;
4351 private $JSMinimizerJShrinkAvailable = TRUE;
4352 private $JumpToPage = NULL;
4353 private $JumpToPageDelay = 0;
4354 private $LogFileName =
"local/logs/site.log";
4355 private $MaxRunningTasksToTrack = 250;
4356 private $MetaTags = array();
4357 private $OutputModificationCallbackInfo;
4358 private $OutputModificationCallbacks = array();
4359 private $OutputModificationPatterns = array();
4360 private $OutputModificationReplacements = array();
4361 private $PageCacheTags = array();
4363 private $PostProcessingFuncs = array();
4364 private $RequeueCurrentTask;
4365 private $RunningInBackground = FALSE;
4366 private $RunningTask;
4367 private $SavedContext;
4368 private $SaveTemplateLocationCache = FALSE;
4369 private $SessionStorage;
4370 private $SessionGcProbability;
4372 private $SuppressHTML = FALSE;
4373 private $SuppressStdPageStartAndEnd = FALSE;
4374 private $TemplateLocationCache;
4375 private $TemplateLocationCacheInterval = 60; # in minutes
4376 private $TemplateLocationCacheExpiration;
4377 private $UnbufferedCallbacks = array();
4378 private $UniqueMetaTags = array();
4379 private $UrlFingerprintBlacklist = array();
4380 private $UseBaseTag = FALSE;
4381 private $SessionInUse = FALSE;
4383 private static $ActiveUI =
"default";
4384 private static $AppName =
"ScoutAF";
4385 private static $DefaultBrowserCacheExpiration = 30; # in seconds
4386 private static $DefaultUI =
"default";
4387 private static $IsAjaxPageLoad;
4388 private static $JSMinCacheDir =
"local/data/caches/JSMin";
4389 private static $NamespaceDirectories = array();
4390 private static $ObjectDirectories = array();
4391 private static $ObjectLocationCache;
4392 private static $ObjectLocationCacheInterval = 60;
4393 private static $ObjectLocationCacheExpiration;
4394 private static $PreferHttpHost = FALSE;
4395 private static $RootUrlOverride = NULL;
4396 private static $SaveObjectLocationCache = FALSE;
4397 private static $ScssCacheDir =
"local/data/caches/SCSS";
4398 private static $SessionLifetime = 1440; # in seconds
4399 private static $UserInterfaceListCache = array();
4400 private static $UserInterfacePathsCache = array();
4402 # offset used to generate page cache tag IDs from numeric tags 4405 # minimum expired session garbage collection probability 4412 private $NoTSR = FALSE;
4414 private $RegisteredEvents = array();
4415 private $KnownPeriodicEvents = array();
4416 private $PeriodicEvents = array(
4417 "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
4418 "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
4419 "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
4420 "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
4421 "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
4423 private $EventPeriods = array(
4424 "EVENT_HOURLY" => 3600,
4425 "EVENT_DAILY" => 86400,
4426 "EVENT_WEEKLY" => 604800,
4427 "EVENT_MONTHLY" => 2592000,
4428 "EVENT_PERIODIC" => 0,
4430 private $UIEvents = array(
4431 "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
4432 "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
4433 "EVENT_PHP_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
4434 "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
4435 "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
4436 "EVENT_PAGE_OUTPUT_FILTER" => self::EVENTTYPE_CHAIN,
4443 private function LoadSettings()
4445 # read settings in from database 4446 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
4447 $this->Settings = $this->DB->FetchRow();
4449 # if settings were not previously initialized 4450 if ($this->Settings === FALSE)
4452 # initialize settings in database 4453 $this->DB->Query(
"INSERT INTO ApplicationFrameworkSettings" 4454 .
" (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
4456 # read new settings in from database 4457 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
4458 $this->Settings = $this->DB->FetchRow();
4460 # bail out if reloading new settings failed 4461 if ($this->Settings === FALSE)
4463 throw new Exception(
4464 "Unable to load application framework settings.");
4468 # if base path was not previously set or we appear to have moved 4469 if (!array_key_exists(
"BasePath", $this->Settings)
4470 || (!strlen($this->Settings[
"BasePath"]))
4471 || (!array_key_exists(
"BasePathCheck", $this->Settings))
4472 || (__FILE__ != $this->Settings[
"BasePathCheck"]))
4474 # attempt to extract base path from Apache .htaccess file 4475 if (is_readable(
".htaccess"))
4477 $Lines = file(
".htaccess");
4478 foreach ($Lines as $Line)
4480 if (preg_match(
"/\\s*RewriteBase\\s+/", $Line))
4482 $Pieces = preg_split(
4483 "/\\s+/", $Line, NULL, PREG_SPLIT_NO_EMPTY);
4484 $BasePath = $Pieces[1];
4489 # if base path was found 4490 if (isset($BasePath))
4492 # save base path locally 4493 $this->Settings[
"BasePath"] = $BasePath;
4495 # save base path to database 4496 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings" 4497 .
" SET BasePath = '".addslashes($BasePath).
"'" 4498 .
", BasePathCheck = '".addslashes(__FILE__).
"'");
4502 # retrieve template location cache 4503 $this->TemplateLocationCache = unserialize(
4504 $this->Settings[
"TemplateLocationCache"]);
4505 $this->TemplateLocationCacheInterval =
4506 $this->Settings[
"TemplateLocationCacheInterval"];
4507 $this->TemplateLocationCacheExpiration =
4508 strtotime($this->Settings[
"TemplateLocationCacheExpiration"]);
4510 # if template location cache looks invalid or has expired 4511 $CurrentTime = time();
4512 if (!is_array($this->TemplateLocationCache)
4513 || !count($this->TemplateLocationCache)
4514 || ($CurrentTime >= $this->TemplateLocationCacheExpiration))
4516 # clear cache and reset cache expiration 4517 $this->TemplateLocationCache = array();
4518 $this->TemplateLocationCacheExpiration =
4519 $CurrentTime + ($this->TemplateLocationCacheInterval * 60);
4520 $this->SaveTemplateLocationCache = TRUE;
4523 # retrieve object location cache 4524 self::$ObjectLocationCache =
4525 unserialize($this->Settings[
"ObjectLocationCache"]);
4526 self::$ObjectLocationCacheInterval =
4527 $this->Settings[
"ObjectLocationCacheInterval"];
4528 self::$ObjectLocationCacheExpiration =
4529 strtotime($this->Settings[
"ObjectLocationCacheExpiration"]);
4531 # if object location cache looks invalid or has expired 4532 if (!is_array(self::$ObjectLocationCache)
4533 || !count(self::$ObjectLocationCache)
4534 || ($CurrentTime >= self::$ObjectLocationCacheExpiration))
4536 # clear cache and reset cache expiration 4537 self::$ObjectLocationCache = array();
4538 self::$ObjectLocationCacheExpiration =
4539 $CurrentTime + (self::$ObjectLocationCacheInterval * 60);
4540 self::$SaveObjectLocationCache = TRUE;
4550 private function RewriteCleanUrls($PageName)
4552 # if URL rewriting is supported by the server 4555 # retrieve current URL and remove base path if present 4558 # for each clean URL mapping 4559 foreach ($this->CleanUrlMappings as $Info)
4561 # if current URL matches clean URL pattern 4562 if (preg_match($Info[
"Pattern"], $Url, $Matches))
4565 $PageName = $Info[
"Page"];
4567 # if $_GET variables specified for clean URL 4568 if ($Info[
"GetVars"] !== NULL)
4570 # for each $_GET variable specified for clean URL 4571 foreach ($Info[
"GetVars"] as $VarName => $VarTemplate)
4573 # start with template for variable value 4574 $Value = $VarTemplate;
4576 # for each subpattern matched in current URL 4577 foreach ($Matches as $Index => $Match)
4579 # if not first (whole) match 4582 # make any substitutions in template 4583 $Value = str_replace(
"$".$Index, $Match, $Value);
4587 # set $_GET variable 4588 $_GET[$VarName] = $Value;
4592 # set flag indicating clean URL mapped 4593 $this->CleanUrlRewritePerformed = TRUE;
4595 # stop looking for a mapping 4601 # return (possibly) updated page name to caller 4617 private function RewriteAlternateDomainUrls($Html)
4619 # if we were loaded via an alternate domain, and we have a 4620 # RootUrlOverride configured to tell us which domain is the 4621 # primary, and if rewriting support is enabled, then we can 4622 # handle URL Rewriting 4623 if ($this->LoadedViaAlternateDomain() &&
4624 self::$RootUrlOverride !== NULL &&
4627 # pull out the configured prefix for this domain 4628 $VHost = $_SERVER[
"SERVER_NAME"];
4629 $ThisPrefix = $this->AlternateDomainPrefixes[$VHost];
4631 # get the URL for the primary domain, including the base path 4632 # (usually the part between the host name and the PHP file name) 4633 $RootUrl = $this->
RootUrl().self::BasePath();
4635 # and figure out what protcol we were using 4636 $Protocol = (isset($_SERVER[
"HTTPS"]) ?
"https" :
"http");
4638 # NB: preg_replace iterates through the configured 4639 # search/replacement pairs, such that the second one 4640 # runs after the first and so on 4642 # the first n-1 patterns below convert any relative 4643 # links in the generated HTML to absolute links using 4644 # our primary domain (e.g., for stylesheets, javascript, 4647 # the nth pattern looks for links that live within the 4648 # path subtree specified by our configured prefix on 4649 # our primary domain, then replaces them with equivalent 4650 # links on our secondary domain 4652 # for example, if our primary domain is 4653 # example.com/MySite and our secondary domain is 4654 # things.example.org/MySite with 'things' as the 4655 # configured prefix, then this last pattern will look 4656 # for example.com/MySite/things and replace it with 4657 # things.example.org/MySite 4658 $RelativePathPatterns = array(
4659 "%src=\"(?!http://|https://)%i",
4660 "%src='(?!http://|https://)%i",
4661 "%href=\"(?!http://|https://)%i",
4662 "%href='(?!http://|https://)%i",
4663 "%action=\"(?!http://|https://)%i",
4664 "%action='(?!http://|https://)%i",
4665 "%@import\s+url\(\"(?!http://|https://)%i",
4666 "%@import\s+url\('(?!http://|https://)%i",
4667 "%src:\s+url\(\"(?!http://|https://)%i",
4668 "%src:\s+url\('(?!http://|https://)%i",
4669 "%@import\s+\"(?!http://|https://)%i",
4670 "%@import\s+'(?!http://|https://)%i",
4671 "%".preg_quote($RootUrl.$ThisPrefix.
"/",
"%").
"%",
4673 $RelativePathReplacements = array(
4678 "action=\"".$RootUrl,
4679 "action='".$RootUrl,
4680 "@import url(\"".$RootUrl,
4681 "@import url('".$RootUrl,
4682 "src: url(\"".$RootUrl,
4683 "src: url('".$RootUrl,
4684 "@import \"".$RootUrl,
4685 "@import '".$RootUrl,
4686 $Protocol.
"://".$VHost.self::BasePath(),
4689 $NewHtml = preg_replace(
4690 $RelativePathPatterns,
4691 $RelativePathReplacements,
4694 # check to make sure relative path fixes didn't fail 4695 $Html = $this->CheckOutputModification(
4697 "alternate domain substitutions");
4707 private function LoadedViaAlternateDomain()
4709 return (isset($_SERVER[
"SERVER_NAME"]) &&
4710 isset($this->AlternateDomainPrefixes[$_SERVER[
"SERVER_NAME"]])) ?
4732 private function FindFile($DirectoryList, $BaseName,
4733 $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
4735 # generate template cache index for this page 4736 $CacheIndex = md5(serialize($DirectoryList))
4737 .self::$DefaultUI.self::$ActiveUI.$BaseName;
4739 # if caching is enabled and we have cached location 4740 if (($this->TemplateLocationCacheInterval > 0)
4741 && array_key_exists($CacheIndex,
4742 $this->TemplateLocationCache))
4744 # use template location from cache 4745 $FoundFileName = $this->TemplateLocationCache[$CacheIndex];
4749 # if suffixes specified and base name does not include suffix 4750 if ($PossibleSuffixes !== NULL
4751 && count($PossibleSuffixes)
4752 && !preg_match(
"/\.[a-zA-Z0-9]+$/", $BaseName))
4754 # add versions of file names with suffixes to file name list 4755 $FileNames = array();
4756 foreach ($PossibleSuffixes as $Suffix)
4758 $FileNames[] = $BaseName.
".".$Suffix;
4763 # use base name as file name 4764 $FileNames = array($BaseName);
4767 # if prefixes specified 4768 if ($PossiblePrefixes !== NULL && count($PossiblePrefixes))
4770 # add versions of file names with prefixes to file name list 4771 $NewFileNames = array();
4772 foreach ($FileNames as $FileName)
4774 foreach ($PossiblePrefixes as $Prefix)
4776 $NewFileNames[] = $Prefix.$FileName;
4779 $FileNames = $NewFileNames;
4782 # expand directory list to include variants 4783 $DirectoryList = $this->ExpandDirectoryList($DirectoryList);
4785 # for each possible location 4786 $FoundFileName = NULL;
4787 foreach ($DirectoryList as $Dir)
4789 # for each possible file name 4790 foreach ($FileNames as $File)
4792 # if template is found at location 4793 if (file_exists($Dir.$File))
4795 # save full template file name and stop looking 4796 $FoundFileName = $Dir.$File;
4802 # save location in cache 4803 $this->TemplateLocationCache[$CacheIndex]
4806 # set flag indicating that cache should be saved 4807 $this->SaveTemplateLocationCache = TRUE;
4810 # return full template file name to caller 4811 return $FoundFileName;
4820 private function ExpandDirectoryList($DirList)
4822 # generate lookup for supplied list 4823 $ExpandedListKey = md5(serialize($DirList)
4824 .self::$DefaultUI.self::$ActiveUI);
4826 # if we already have expanded version of supplied list 4827 if (isset($this->ExpandedDirectoryLists[$ExpandedListKey]))
4829 # return expanded version to caller 4830 return $this->ExpandedDirectoryLists[$ExpandedListKey];
4833 # for each directory in list 4834 $ExpDirList = array();
4835 foreach ($DirList as $Dir)
4837 # if directory includes substitution keyword 4838 if ((strpos($Dir,
"%DEFAULTUI%") !== FALSE)
4839 || (strpos($Dir,
"%ACTIVEUI%") !== FALSE))
4841 # start with empty new list segment 4842 $ExpDirListSegment = array();
4844 # use default values for initial parent 4845 $ParentInterface = array(self::$ActiveUI, self::$DefaultUI);
4849 # substitute in for keyword on parent 4850 $CurrDir = str_replace(array(
"%ACTIVEUI%",
"%DEFAULTUI%"),
4851 $ParentInterface, $Dir);
4853 # add local version of parent directory to new list segment 4854 $ExpDirListSegment[] =
"local/".$CurrDir;
4856 # add parent directory to new list segment 4857 $ExpDirListSegment[] = $CurrDir;
4859 # look for new parent interface 4860 $ParentInterface = $this->GetInterfaceSetting(
4861 $CurrDir,
"ParentInterface");
4863 # repeat if parent is available 4864 }
while (strlen($ParentInterface));
4866 # add new list segment to expanded list 4867 $ExpDirList = array_merge($ExpDirList, $ExpDirListSegment);
4871 # add local version of directory to expanded list 4872 $ExpDirList[] =
"local/".$Dir;
4874 # add directory to expanded list 4875 $ExpDirList[] = $Dir;
4879 # return expanded version to caller 4880 $this->ExpandedDirectoryLists[$ExpandedListKey] = $ExpDirList;
4881 return $this->ExpandedDirectoryLists[$ExpandedListKey];
4892 private function GetInterfaceSetting($InterfaceDir, $SettingName = NULL)
4894 # extract canonical interface name and base interface directory 4895 preg_match(
"%(.*interface/)([^/]+)%", $InterfaceDir, $Matches);
4896 $InterfaceDir = (count($Matches) > 2)
4897 ? $Matches[1].$Matches[2] : $InterfaceDir;
4898 $InterfaceName = (count($Matches) > 2)
4899 ? $Matches[2] :
"UNKNOWN";
4901 # if we do not have settings for interface 4902 if (!isset($this->InterfaceSettings[$InterfaceName]))
4904 # load default values for settings 4905 $this->InterfaceSettings[$InterfaceName] = array(
4910 # if directory takes precedence over existing settings source 4911 # ("takes precendence" == is more local == longer directory length) 4912 if (strlen($InterfaceDir)
4913 > strlen($this->InterfaceSettings[$InterfaceName][
"Source"]))
4915 # if settings file exists in directory 4916 $SettingsFile = $InterfaceDir.
"/interface.ini";
4917 if (is_readable($SettingsFile))
4919 # read in values from file 4920 $NewSettings = parse_ini_file($SettingsFile);
4922 # merge in values with existing settings 4923 $this->InterfaceSettings[$InterfaceName] = array_merge(
4924 $this->InterfaceSettings[$InterfaceName], $NewSettings);
4926 # save new source of settings 4927 $this->InterfaceSettings[$InterfaceName][
"Source"] = $InterfaceDir;
4931 # return interface settings to caller 4933 ? (isset($this->InterfaceSettings[$InterfaceName][$SettingName])
4934 ? $this->InterfaceSettings[$InterfaceName][$SettingName]
4936 : $this->InterfaceSettings[$InterfaceName];
4947 private function CompileScssFile($SrcFile)
4949 # build path to CSS file 4950 $DstFile = self::$ScssCacheDir.
"/".dirname($SrcFile)
4951 .
"/".basename($SrcFile);
4952 $DstFile = substr_replace($DstFile,
"css", -4);
4954 # if SCSS file is newer than CSS file 4955 if (!file_exists($DstFile)
4956 || (filemtime($SrcFile) > filemtime($DstFile)))
4958 # attempt to create CSS cache subdirectory if not present 4959 if (!is_dir(dirname($DstFile)))
4961 @mkdir(dirname($DstFile), 0777, TRUE);
4964 # if CSS cache directory and CSS file path appear writable 4965 static $CacheDirIsWritable;
4966 if (!isset($CacheDirIsWritable))
4967 { $CacheDirIsWritable = is_writable(self::$ScssCacheDir); }
4968 if (is_writable($DstFile)
4969 || (!file_exists($DstFile) && $CacheDirIsWritable))
4971 # load SCSS and compile to CSS 4972 $ScssCode = file_get_contents($SrcFile);
4973 $ScssCompiler =
new scssc();
4975 ?
"scss_formatter_compressed" :
"scss_formatter");
4978 $CssCode = $ScssCompiler->compile($ScssCode);
4980 # add fingerprinting for URLs in CSS 4981 $this->CssUrlFingerprintPath = dirname($SrcFile);
4982 $CssCode = preg_replace_callback(
4983 "/url\((['\"]*)(.+)\.([a-z]+)(['\"]*)\)/",
4984 array($this,
"CssUrlFingerprintInsertion"),
4987 # strip out comments from CSS (if requested) 4990 $CssCode = preg_replace(
'!/\*[^*]*\*+([^/][^*]*\*+)*/!',
4994 # write out CSS file 4995 file_put_contents($DstFile, $CssCode);
4997 catch (Exception $Ex)
4999 $this->
LogError(self::LOGLVL_ERROR,
5000 "Error compiling SCSS file ".$SrcFile.
": " 5001 .$Ex->getMessage());
5007 # log error and set CSS file path to indicate failure 5008 $this->
LogError(self::LOGLVL_ERROR,
5009 "Unable to write out CSS file (compiled from SCSS) to " 5015 # return CSS file path to caller 5026 private function MinimizeJavascriptFile($SrcFile)
5028 # bail out if file is on exclusion list 5029 foreach ($this->DoNotMinimizeList as $DNMFile)
5031 if (($SrcFile == $DNMFile) || (basename($SrcFile) == $DNMFile))
5037 # build path to minimized file 5038 $DstFile = self::$JSMinCacheDir.
"/".dirname($SrcFile)
5039 .
"/".basename($SrcFile);
5040 $DstFile = substr_replace($DstFile,
".min", -3, 0);
5042 # if original file is newer than minimized file 5043 if (!file_exists($DstFile)
5044 || (filemtime($SrcFile) > filemtime($DstFile)))
5046 # attempt to create cache subdirectory if not present 5047 if (!is_dir(dirname($DstFile)))
5049 @mkdir(dirname($DstFile), 0777, TRUE);
5052 # if cache directory and minimized file path appear writable 5053 static $CacheDirIsWritable;
5054 if (!isset($CacheDirIsWritable))
5055 { $CacheDirIsWritable = is_writable(self::$JSMinCacheDir); }
5056 if (is_writable($DstFile)
5057 || (!file_exists($DstFile) && $CacheDirIsWritable))
5059 # load JavaScript code 5060 $Code = file_get_contents($SrcFile);
5062 # decide which minimizer to use 5063 if ($this->JSMinimizerJavaScriptPackerAvailable
5064 && $this->JSMinimizerJShrinkAvailable)
5066 $Minimizer = (strlen($Code) < 5000)
5067 ?
"JShrink" :
"JavaScriptPacker";
5069 elseif ($this->JSMinimizerJShrinkAvailable)
5071 $Minimizer =
"JShrink";
5075 $Minimizer =
"NONE";
5081 case "JavaScriptMinimizer":
5083 $MinimizedCode = $Packer->pack();
5091 catch (Exception $Exception)
5093 unset($MinimizedCode);
5094 $MinimizeError = $Exception->getMessage();
5099 # if minimization succeeded 5100 if (isset($MinimizedCode))
5102 # write out minimized file 5103 file_put_contents($DstFile, $MinimizedCode);
5107 # log error and set destination file path to indicate failure 5108 $ErrMsg =
"Unable to minimize JavaScript file ".$SrcFile;
5109 if (isset($MinimizeError))
5111 $ErrMsg .=
" (".$MinimizeError.
")";
5113 $this->
LogError(self::LOGLVL_ERROR, $ErrMsg);
5119 # log error and set destination file path to indicate failure 5120 $this->
LogError(self::LOGLVL_ERROR,
5121 "Unable to write out minimized JavaScript to file ".$DstFile);
5126 # return CSS file path to caller 5137 private function CssUrlFingerprintInsertion($Matches)
5139 # generate fingerprint string from CSS file modification time 5140 $FileName = realpath($this->CssUrlFingerprintPath.
"/".
5141 $Matches[2].
".".$Matches[3]);
5142 $MTime = filemtime($FileName);
5143 $Fingerprint = sprintf(
"%06X", ($MTime % 0xFFFFFF));
5145 # build URL string with fingerprint and return it to caller 5146 return "url(".$Matches[1].$Matches[2].
".".$Fingerprint
5147 .
".".$Matches[3].$Matches[4].
")";
5157 private function GetRequiredFilesNotYetLoaded($PageContentFile)
5159 # start out assuming no files required 5160 $RequiredFiles = array();
5162 # if page content file supplied 5163 if ($PageContentFile)
5165 # if file containing list of required files is available 5166 $Path = dirname($PageContentFile);
5167 $RequireListFile = $Path.
"/REQUIRES";
5168 if (file_exists($RequireListFile))
5170 # read in list of required files 5171 $RequestedFiles = file($RequireListFile);
5173 # for each line in required file list 5174 foreach ($RequestedFiles as $Line)
5176 # if line is not a comment 5177 $Line = trim($Line);
5178 if (!preg_match(
"/^#/", $Line))
5180 # if file has not already been loaded 5181 if (!in_array($Line, $this->FoundUIFiles))
5183 # add to list of required files 5184 $RequiredFiles[$Line] = self::ORDER_MIDDLE;
5191 # add in additional required files if any 5192 if (count($this->AdditionalRequiredUIFiles))
5194 # make sure there are no duplicates 5195 $AdditionalRequiredUIFiles = array_unique(
5196 $this->AdditionalRequiredUIFiles);
5198 $RequiredFiles = array_merge(
5199 $RequiredFiles, $this->AdditionalRequiredUIFiles);
5202 # return list of required files to caller 5203 return $RequiredFiles;
5214 private function SubBrowserIntoFileNames($FileNames)
5216 # if a browser detection function has been made available 5217 $UpdatedFileNames = array();
5218 if (is_callable($this->BrowserDetectFunc))
5220 # call function to get browser list 5221 $Browsers = call_user_func($this->BrowserDetectFunc);
5223 # for each required file 5224 foreach ($FileNames as $FileName => $Value)
5226 # if file name includes browser keyword 5227 if (preg_match(
"/%BROWSER%/", $FileName))
5230 foreach ($Browsers as $Browser)
5232 # substitute in browser name and add to new file list 5233 $NewFileName = preg_replace(
5234 "/%BROWSER%/", $Browser, $FileName);
5235 $UpdatedFileNames[$NewFileName] = $Value;
5240 # add to new file list 5241 $UpdatedFileNames[$FileName] = $Value;
5247 # filter out any files with browser keyword in their name 5248 foreach ($FileNames as $FileName => $Value)
5250 if (!preg_match(
"/%BROWSER%/", $FileName))
5252 $UpdatedFileNames[$FileName] = $Value;
5257 return $UpdatedFileNames;
5265 private function AddMetaTagsToPageOutput($PageOutput)
5267 # start with unconditional (non-unique) tags 5268 $TagsToAdd = $this->MetaTags;
5270 # for each unique tag 5271 foreach ($this->UniqueMetaTags as $UniqueMetaTag)
5273 $Attribs = $UniqueMetaTag[
"Attribs"];
5274 $UniqueAttribs = $UniqueMetaTag[
"UniqueAttribs"];
5276 # if no unique attributes specified 5277 if ($UniqueAttribs === NULL)
5279 # use first attribute as unique attribute 5280 $UniqueAttribs = array_slice($Attribs, 0, 1);
5283 # for each already-queued tag 5284 # (look for meta tags that match all attributes in 5285 # the current unique tag) 5286 foreach ($TagsToAdd as $TagAttribs)
5288 # for each attribute in current unique tag 5289 # (look for attributes in the current unique tag that do 5290 # not match attributes in the this queued tag) 5291 foreach ($UniqueAttribs as $UniqueName => $UniqueValue)
5293 # if unique attribute is not found in queued tag 5294 # or queued tag attribute has a different value 5295 if (!isset($TagAttribs[$UniqueName])
5296 || ($TagAttribs[$UniqueName] != $UniqueValue))
5298 # skip to next queued tag 5299 # (some attribute in the current unique tag 5300 # was not found in the queued tag) 5305 # skip to next unique tag 5306 # (all attributes in the current unique tag were found 5307 # in the queued tag, so do not queue this unique tag) 5311 # generate potential combinations of unique attributes 5313 array_keys($UniqueAttribs));
5315 # for each combination of unique attributes 5316 foreach ($UniqueAttribNameCombos as $UniqueNameCombo)
5318 # for each attribute in combination 5319 $AttribStrings = array();
5320 foreach ($UniqueNameCombo as $UniqueName)
5322 # add attrib/value string to list 5323 $AttribStrings[] = $UniqueName.
"=\"" 5324 .htmlspecialchars($UniqueAttribs[$UniqueName]).
"\"";
5327 # build search string from list of attribute pairs 5328 $SearchString =
"<meta ".implode(
" ", $AttribStrings);
5330 # if search string appears in page output 5331 if (strpos($PageOutput, $SearchString) !== FALSE)
5333 # skip to next unique tag 5337 # repeat search with single quotes instead of double quotes 5338 $SearchString = strtr($SearchString,
'"',
"'");
5339 if (strpos($PageOutput, $SearchString) !== FALSE)
5341 # skip to next unique tag 5346 # unique tag was not found in page output, so add it to inserted tags 5347 $TagsToAdd[] = $Attribs;
5350 # if there are meta tags to be added 5351 if (count($TagsToAdd))
5353 # start with an empty segment 5357 foreach ($TagsToAdd as $Attribs)
5359 # assemble tag and add it to the segment 5360 $Section .=
"<meta";
5361 foreach ($Attribs as $AttribName => $AttribValue)
5363 $Section .=
" ".$AttribName.
"=\"" 5364 .htmlspecialchars(trim($AttribValue)).
"\"";
5366 $Section .=
" />\n";
5369 # if standard page start and end have been disabled 5370 if ($this->SuppressStdPageStartAndEnd)
5372 # add segment to beginning of page output 5373 $PageOutput = $Section.$PageOutput;
5377 # insert segment at beginning of HTML head section in page output 5378 $PageOutput = preg_replace(
"#<head>#i",
5379 "<head>\n".$Section, $PageOutput, 1);
5383 # return (potentially modified) page output to caller 5394 private function AddFileTagsToPageOutput($PageOutput, $Files)
5396 # substitute browser name into names of required files as appropriate 5397 $Files = $this->SubBrowserIntoFileNames($Files);
5399 # initialize content sections 5401 self::ORDER_FIRST =>
"",
5402 self::ORDER_MIDDLE =>
"",
5403 self::ORDER_LAST =>
"",
5406 self::ORDER_FIRST =>
"",
5407 self::ORDER_MIDDLE =>
"",
5408 self::ORDER_LAST =>
"",
5411 # for each required file 5412 foreach ($Files as $File => $Order)
5414 # locate specific file to use 5415 $FilePath = $this->
GUIFile($File);
5420 # generate tag for file 5421 $Tag = $this->GetUIFileLoadingTag($FilePath);
5423 # add file to HTML output based on file type 5428 $HeadContent[$Order] .= $Tag.
"\n";
5431 case self::FT_JAVASCRIPT:
5432 $BodyContent[$Order] .= $Tag.
"\n";
5438 # add content to head 5439 $Replacement = $HeadContent[self::ORDER_MIDDLE]
5440 .$HeadContent[self::ORDER_LAST];
5441 $UpdatedPageOutput = str_ireplace(
"</head>",
5442 $Replacement.
"</head>",
5443 $PageOutput, $ReplacementCount);
5444 # (if no </head> tag was found, just prepend tags to page content) 5445 if ($ReplacementCount == 0)
5447 $PageOutput = $Replacement.$PageOutput;
5449 # (else if multiple </head> tags found, only prepend tags to the first) 5450 elseif ($ReplacementCount > 1)
5452 $PageOutput = preg_replace(
"#</head>#i",
5453 $Replacement.
"</head>",
5458 $PageOutput = $UpdatedPageOutput;
5460 $Replacement = $HeadContent[self::ORDER_FIRST];
5461 $UpdatedPageOutput = str_ireplace(
"<head>",
5462 "<head>\n".$Replacement,
5463 $PageOutput, $ReplacementCount);
5464 # (if no <head> tag was found, just prepend tags to page content) 5465 if ($ReplacementCount == 0)
5467 $PageOutput = $Replacement.$PageOutput;
5469 # (else if multiple <head> tags found, only append tags to the first) 5470 elseif ($ReplacementCount > 1)
5472 $PageOutput = preg_replace(
"#<head>#i",
5473 "<head>\n".$Replacement,
5478 $PageOutput = $UpdatedPageOutput;
5481 # add content to body 5482 $Replacement = $BodyContent[self::ORDER_FIRST];
5483 $PageOutput = preg_replace(
"#<body([^>]*)>#i",
5484 "<body\\1>\n".$Replacement,
5485 $PageOutput, 1, $ReplacementCount);
5486 # (if no <body> tag was found, just append tags to page content) 5487 if ($ReplacementCount == 0)
5489 $PageOutput = $PageOutput.$Replacement;
5491 $Replacement = $BodyContent[self::ORDER_MIDDLE]
5492 .$BodyContent[self::ORDER_LAST];
5493 $UpdatedPageOutput = str_ireplace(
"</body>",
5494 $Replacement.
"\n</body>",
5495 $PageOutput, $ReplacementCount);
5496 # (if no </body> tag was found, just append tags to page content) 5497 if ($ReplacementCount == 0)
5499 $PageOutput = $PageOutput.$Replacement;
5501 # (else if multiple </body> tags found, only prepend tag to the first) 5502 elseif ($ReplacementCount > 1)
5504 $PageOutput = preg_replace(
"#</body>#i",
5505 $Replacement.
"\n</body>",
5510 $PageOutput = $UpdatedPageOutput;
5526 private function GetUIFileLoadingTag($FileName, $AdditionalAttributes = NULL)
5528 # pad additional attributes if supplied 5529 $AddAttribs = $AdditionalAttributes ?
" ".$AdditionalAttributes :
"";
5531 # retrieve type of UI file 5534 # construct tag based on file type 5538 $Tag =
" <link rel=\"stylesheet\" type=\"text/css\"" 5539 .
" media=\"all\" href=\"".$FileName.
"\"" 5540 .$AddAttribs.
" />\n";
5543 case self::FT_JAVASCRIPT:
5544 $Tag =
" <script type=\"text/javascript\"" 5545 .
" src=\"".$FileName.
"\"" 5546 .$AddAttribs.
"></script>\n";
5549 case self::FT_IMAGE:
5550 $Tag =
"<img src=\"".$FileName.
"\"".$AddAttribs.
">";
5558 # return constructed tag to caller 5566 private function AutoloadObjects($ClassName)
5568 # if caching is not turned off 5569 # and we have a cached location for class 5570 # and file at cached location is readable 5571 if ((self::$ObjectLocationCacheInterval > 0)
5572 && array_key_exists($ClassName,
5573 self::$ObjectLocationCache)
5574 && is_readable(self::$ObjectLocationCache[$ClassName]))
5576 # use object location from cache 5577 require_once(self::$ObjectLocationCache[$ClassName]);
5581 # for each possible object file directory 5583 foreach (self::$ObjectDirectories as $Location => $Info)
5585 # make any needed replacements in directory path 5586 $Location = str_replace(array(
"%ACTIVEUI%",
"%DEFAULTUI%"),
5587 array(self::$ActiveUI, self::$DefaultUI), $Location);
5589 # if directory looks valid 5590 if (is_dir($Location))
5592 # pass class name through callback (if supplied) 5593 $ClassFileName = $ClassName;
5594 if (is_callable($Info[
"Callback"]))
5596 $ClassFileName = $Info[
"Callback"]($ClassFileName);
5599 # strip off any namespace prefix 5600 foreach ($Info[
"NamespacePrefixes"] as $Prefix)
5602 if (strpos($ClassFileName, $Prefix) === 0)
5604 $ClassFileName = substr($ClassFileName, strlen($Prefix));
5609 # strip off any leading namespace separator 5610 if (strpos($ClassFileName,
"\\") === 0)
5612 $ClassFileName = substr($ClassFileName, 1);
5615 # convert any namespace separators to directory separators 5616 $ClassFileName = str_replace(
"\\",
"/", $ClassFileName);
5618 # finish building class file name 5619 $ClassFileName = $ClassFileName.
".php";
5621 # read in directory contents if not already retrieved 5622 if (!isset($FileLists[$Location]))
5624 $FileLists[$Location] = self::ReadDirectoryTree(
5625 $Location,
'/^.+\.php$/i');
5628 # for each file in target directory 5629 foreach ($FileLists[$Location] as $FileName)
5631 # if file matches our target object file name 5632 if ($FileName == $ClassFileName)
5634 # include object file 5635 require_once($Location.$FileName);
5637 # save location to cache 5638 self::$ObjectLocationCache[$ClassName]
5639 = $Location.$FileName;
5641 # set flag indicating that cache should be saved 5642 self::$SaveObjectLocationCache = TRUE;
5660 private static function ReadDirectoryTree($Directory, $Pattern)
5662 $CurrentDir = getcwd();
5664 $DirIter =
new RecursiveDirectoryIterator(
".");
5665 $IterIter =
new RecursiveIteratorIterator($DirIter);
5666 $RegexResults =
new RegexIterator($IterIter, $Pattern,
5667 RecursiveRegexIterator::GET_MATCH);
5668 $FileList = array();
5669 foreach ($RegexResults as $Result)
5671 $FileList[] = substr($Result[0], 2);
5681 private function LoadUIFunctions()
5684 "local/interface/%ACTIVEUI%/include",
5685 "interface/%ACTIVEUI%/include",
5686 "local/interface/%DEFAULTUI%/include",
5687 "interface/%DEFAULTUI%/include",
5689 foreach ($Dirs as $Dir)
5691 $Dir = str_replace(array(
"%ACTIVEUI%",
"%DEFAULTUI%"),
5692 array(self::$ActiveUI, self::$DefaultUI), $Dir);
5695 $FileNames = scandir($Dir);
5696 foreach ($FileNames as $FileName)
5698 if (preg_match(
"/^F-([A-Za-z0-9_]+)\.php/",
5699 $FileName, $Matches)
5700 || preg_match(
"/^F-([A-Za-z0-9_]+)\.html/",
5701 $FileName, $Matches))
5703 if (!function_exists($Matches[1]))
5705 include_once($Dir.
"/".$FileName);
5718 private function ProcessPeriodicEvent($EventName, $Callback)
5720 # retrieve last execution time for event if available 5721 $Signature = self::GetCallbackSignature($Callback);
5722 $LastRun = $this->DB->Query(
"SELECT LastRunAt FROM PeriodicEvents" 5723 .
" WHERE Signature = '".addslashes($Signature).
"'",
"LastRunAt");
5725 # determine whether enough time has passed for event to execute 5726 $ShouldExecute = (($LastRun === NULL)
5727 || (time() > (strtotime($LastRun) + $this->EventPeriods[$EventName])))
5730 # if event should run 5733 # add event to task queue 5734 $WrapperCallback = array(
"ApplicationFramework",
"RunPeriodicEvent");
5735 $WrapperParameters = array(
5736 $EventName, $Callback, array(
"LastRunAt" => $LastRun));
5740 # add event to list of periodic events 5741 $this->KnownPeriodicEvents[$Signature] = array(
5742 "Period" => $EventName,
5743 "Callback" => $Callback,
5744 "Queued" => $ShouldExecute);
5752 private static function GetCallbackSignature($Callback)
5754 return !is_array($Callback) ? $Callback
5755 : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
5763 private function PrepForTSR()
5765 # if HTML has been output and it's time to launch another task 5766 # (only TSR if HTML has been output because otherwise browsers 5767 # may misbehave after connection is closed) 5768 if ((PHP_SAPI !=
"cli")
5769 && ($this->JumpToPage || !$this->SuppressHTML)
5770 && !$this->LoadedViaAlternateDomain()
5771 && (time() > (strtotime($this->Settings[
"LastTaskRunAt"])
5773 / $this->Settings[
"MaxTasksRunning"]) + 5))
5775 && $this->Settings[
"TaskExecutionEnabled"])
5777 # begin buffering output for TSR 5780 # let caller know it is time to launch another task 5785 # let caller know it is not time to launch another task 5794 private function LaunchTSR()
5796 # set headers to close out connection to browser 5799 ignore_user_abort(TRUE);
5800 header(
"Connection: close");
5801 header(
"Content-Length: ".ob_get_length());
5804 # output buffered content 5805 while (ob_get_level()) { ob_end_flush(); }
5808 # write out any outstanding data and end HTTP session 5809 session_write_close();
5811 # set flag indicating that we are now running in background 5812 $this->RunningInBackground = TRUE;
5814 # handle garbage collection for session data 5815 if (isset($this->SessionStorage) &&
5816 (rand()/getrandmax()) <= $this->SessionGcProbability)
5818 # determine when sessions will expire 5819 $ExpiredTime = strtotime(
"-". self::$SessionLifetime.
" seconds");
5821 # iterate over files in the session directory with a DirectoryIterator 5822 # NB: we cannot use scandir() here because it reads the 5823 # entire list of files into memory and may exceed the memory 5824 # limit for directories with very many files 5825 $DI =
new DirectoryIterator($this->SessionStorage);
5826 while ($DI->valid())
5828 if ((strpos($DI->getFilename(),
"sess_") === 0) &&
5830 $DI->getCTime() < $ExpiredTime)
5832 unlink($DI->getPathname());
5839 # if there is still a task in the queue 5842 # tell PHP to garbage collect to give as much memory as possible for tasks 5843 gc_collect_cycles();
5845 # turn on output buffering to (hopefully) record any crash output 5848 # lock tables and grab last task run time to double check 5849 $this->DB->Query(
"LOCK TABLES ApplicationFrameworkSettings WRITE");
5850 $this->LoadSettings();
5852 # if still time to launch another task 5853 if (time() > (strtotime($this->Settings[
"LastTaskRunAt"])
5855 / $this->Settings[
"MaxTasksRunning"]) + 5))
5857 # update the "last run" time and release tables 5858 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings" 5859 .
" SET LastTaskRunAt = '".date(
"Y-m-d H:i:s").
"'");
5860 $this->DB->Query(
"UNLOCK TABLES");
5862 # run tasks while there is a task in the queue 5863 # and enough time and memory left 5867 $this->RunNextTask();
5869 # calculate percentage of memory still available 5870 $PercentFreeMem = (self::GetFreeMemory()
5871 / self::GetPhpMemoryLimit()) * 100;
5875 && ($PercentFreeMem > $this->BackgroundTaskMinFreeMemPercent));
5877 $this->ResetTaskIdGeneratorIfNecessary();
5882 $this->DB->Query(
"UNLOCK TABLES");
5896 private function GetTaskList($DBQuery, $Count, $Offset)
5898 $this->DB->Query($DBQuery.
" LIMIT ".intval($Offset).
",".intval($Count));
5900 while ($Row = $this->DB->FetchRow())
5902 $Tasks[$Row[
"TaskId"]] = $Row;
5903 if ($Row[
"Callback"] ==
5904 serialize(array(
"ApplicationFramework",
"RunPeriodicEvent")))
5906 $WrappedCallback = unserialize($Row[
"Parameters"]);
5907 $Tasks[$Row[
"TaskId"]][
"Callback"] = $WrappedCallback[1];
5908 $Tasks[$Row[
"TaskId"]][
"Parameters"] = NULL;
5912 $Tasks[$Row[
"TaskId"]][
"Callback"] = unserialize($Row[
"Callback"]);
5913 $Tasks[$Row[
"TaskId"]][
"Parameters"] = unserialize($Row[
"Parameters"]);
5922 private function RunNextTask()
5924 # lock tables to prevent same task from being run by multiple sessions 5925 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
5927 # look for task at head of queue 5928 $this->DB->Query(
"SELECT * FROM TaskQueue ORDER BY Priority, TaskId LIMIT 1");
5929 $Task = $this->DB->FetchRow();
5931 # if there was a task available 5934 # move task from queue to running tasks list 5935 $this->DB->Query(
"INSERT INTO RunningTasks " 5936 .
"(TaskId,Callback,Parameters,Priority,Description) " 5937 .
"SELECT * FROM TaskQueue WHERE TaskId = " 5938 .intval($Task[
"TaskId"]));
5939 $this->DB->Query(
"DELETE FROM TaskQueue WHERE TaskId = " 5940 .intval($Task[
"TaskId"]));
5942 # release table locks to again allow other sessions to run tasks 5943 $this->DB->Query(
"UNLOCK TABLES");
5945 # unpack stored task info 5946 $TaskId = $Task[
"TaskId"];
5947 $Callback = unserialize($Task[
"Callback"]);
5948 $Parameters = unserialize($Task[
"Parameters"]);
5950 # attempt to load task callback if not already available 5953 # clear task requeue flag 5956 # save amount of free memory for later comparison 5957 $BeforeFreeMem = self::GetFreeMemory();
5960 $this->RunningTask = $Task;
5963 call_user_func_array($Callback, $Parameters);
5967 call_user_func($Callback);
5969 unset($this->RunningTask);
5971 # log if task leaked significant memory 5972 $this->LogTaskMemoryLeakIfAny($TaskId, $BeforeFreeMem);
5974 # if task requeue requested 5977 # move task from running tasks list to queue 5978 $this->RequeueRunningTask($TaskId);
5982 # remove task from running tasks list 5983 $this->DB->Query(
"DELETE FROM RunningTasks" 5984 .
" WHERE TaskId = ".intval($TaskId));
5987 # prune running tasks list if necessary 5988 $RunningTasksCount = $this->DB->Query(
5989 "SELECT COUNT(*) AS TaskCount FROM RunningTasks",
"TaskCount");
5990 if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
5992 $this->DB->Query(
"DELETE FROM RunningTasks ORDER BY StartedAt" 5993 .
" LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
5998 # release table locks to again allow other sessions to run tasks 5999 $this->DB->Query(
"UNLOCK TABLES");
6008 private function RequeueRunningTask($TaskId)
6010 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
6011 $this->DB->Query(
"INSERT INTO TaskQueue" 6012 .
" (Callback,Parameters,Priority,Description)" 6013 .
" SELECT Callback,Parameters,Priority,Description" 6014 .
" FROM RunningTasks WHERE TaskId = ".intval($TaskId));
6015 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
6016 $this->DB->Query(
"UNLOCK TABLES");
6024 private function ResetTaskIdGeneratorIfNecessary()
6026 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE");
6030 $this->DB->Query(
"TRUNCATE TABLE TaskQueue");
6032 $this->DB->Query(
"UNLOCK TABLES");
6041 private function LogTaskMemoryLeakIfAny($TaskId, $StartingFreeMemory)
6043 # tell PHP to garbage collect to free up any memory no longer used 6044 gc_collect_cycles();
6046 # calculate the logging threshold 6047 $LeakThreshold = self::GetPhpMemoryLimit()
6048 * ($this->BackgroundTaskMemLeakLogThreshold / 100);
6050 # calculate the amount of memory used by task 6051 $EndingFreeMemory = self::GetFreeMemory();
6052 $MemoryUsed = $StartingFreeMemory - $EndingFreeMemory;
6054 # if amount of memory used is over threshold 6055 if ($MemoryUsed > $LeakThreshold)
6058 $TaskSynopsis = self::GetTaskCallbackSynopsis($this->
GetTask($TaskId));
6059 $this->
LogError(self::LOGLVL_DEBUG,
"Task ".$TaskSynopsis.
" leaked " 6060 .number_format($MemoryUsed).
" bytes.");
6071 # attempt to remove any memory limits 6073 ini_set(
"memory_limit", -1);
6075 # if there is a background task currently running 6076 if (isset($this->RunningTask))
6078 # add info about current page load 6080 $CrashInfo[
"FreeMemory"] = $FreeMemory;
6081 $CrashInfo[
"REMOTE_ADDR"] = $_SERVER[
"REMOTE_ADDR"];
6082 $CrashInfo[
"REQUEST_URI"] = $_SERVER[
"REQUEST_URI"];
6083 if (isset($_SERVER[
"REQUEST_TIME"]))
6085 $CrashInfo[
"REQUEST_TIME"] = $_SERVER[
"REQUEST_TIME"];
6087 if (isset($_SERVER[
"REMOTE_HOST"]))
6089 $CrashInfo[
"REMOTE_HOST"] = $_SERVER[
"REMOTE_HOST"];
6092 # add info about error that caused crash (if available) 6093 if (function_exists(
"error_get_last"))
6095 $CrashInfo[
"LastError"] = error_get_last();
6098 # add info about current output buffer contents (if available) 6099 if (ob_get_length() !== FALSE)
6101 $CrashInfo[
"OutputBuffer"] = ob_get_contents();
6104 # if backtrace info is available for the crash 6105 $Backtrace = debug_backtrace();
6106 if (count($Backtrace) > 1)
6108 # discard the current context from the backtrace 6109 array_shift($Backtrace);
6111 # add the backtrace to the crash info 6112 $CrashInfo[
"Backtrace"] = $Backtrace;
6114 # else if saved backtrace info is available 6115 elseif (isset($this->SavedContext))
6117 # add the saved backtrace to the crash info 6118 $CrashInfo[
"Backtrace"] = $this->SavedContext;
6121 # save crash info for currently running task 6123 $DB->Query(
"UPDATE RunningTasks SET CrashInfo = '" 6124 .addslashes(serialize($CrashInfo))
6125 .
"' WHERE TaskId = ".intval($this->RunningTask[
"TaskId"]));
6148 private function AddToDirList($DirList, $Dir, $SearchLast, $SkipSlashCheck)
6150 # convert incoming directory to array of directories (if needed) 6151 $Dirs = is_array($Dir) ? $Dir : array($Dir);
6153 # reverse array so directories are searched in specified order 6154 $Dirs = array_reverse($Dirs);
6156 # for each directory 6157 foreach ($Dirs as $Location)
6159 # make sure directory includes trailing slash 6160 if (!$SkipSlashCheck)
6162 $Location = $Location
6163 .((substr($Location, -1) !=
"/") ?
"/" :
"");
6166 # remove directory from list if already present 6167 if (in_array($Location, $DirList))
6169 $DirList = array_diff(
6170 $DirList, array($Location));
6173 # add directory to list of directories 6176 array_push($DirList, $Location);
6180 array_unshift($DirList, $Location);
6184 # return updated directory list to caller 6194 private function OutputModificationCallbackShell($Matches)
6196 # call previously-stored external function 6197 return call_user_func($this->OutputModificationCallbackInfo[
"Callback"],
6199 $this->OutputModificationCallbackInfo[
"Pattern"],
6200 $this->OutputModificationCallbackInfo[
"Page"],
6201 $this->OutputModificationCallbackInfo[
"SearchPattern"]);
6212 private function CheckOutputModification($Original, $Modified, $ErrorInfo)
6214 # if error was reported by regex engine 6215 if (preg_last_error() !== PREG_NO_ERROR)
6218 $this->
LogError(self::LOGLVL_ERROR,
6219 "Error reported by regex engine when modifying output." 6220 .
" (".$ErrorInfo.
")");
6222 # use unmodified version of output 6223 $OutputToUse = $Original;
6225 # else if modification reduced output by more than threshold 6226 elseif ((strlen(trim($Modified)) / strlen(trim($Original)))
6227 < self::OUTPUT_MODIFICATION_THRESHOLD)
6230 $this->
LogError(self::LOGLVL_WARNING,
6231 "Content reduced below acceptable threshold while modifying output." 6232 .
" (".$ErrorInfo.
")");
6234 # use unmodified version of output 6235 $OutputToUse = $Original;
6239 # use modified version of output 6240 $OutputToUse = $Modified;
6243 # return output to use to caller 6244 return $OutputToUse;
6259 private function UpdateSetting(
6260 $FieldName, $NewValue =
DB_NOVALUE, $Persistent = TRUE)
6262 static $LocalSettings;
6267 $LocalSettings[$FieldName] = $this->DB->UpdateValue(
6268 "ApplicationFrameworkSettings",
6269 $FieldName, $NewValue, NULL, $this->Settings);
6273 $LocalSettings[$FieldName] = $NewValue;
6276 elseif (!isset($LocalSettings[$FieldName]))
6278 $LocalSettings[$FieldName] = $this->DB->UpdateValue(
6279 "ApplicationFrameworkSettings",
6280 $FieldName, $NewValue, NULL, $this->Settings);
6282 return $LocalSettings[$FieldName];
6294 private static function IncludeFile($_AF_File, $_AF_ContextVars = array())
6297 foreach ($_AF_ContextVars as $_AF_VarName => $_AF_VarValue)
6299 $$_AF_VarName = $_AF_VarValue;
6301 unset($_AF_VarName);
6302 unset($_AF_VarValue);
6303 unset($_AF_ContextVars);
6305 # add variables to context that we assume are always available 6306 $AF = $GLOBALS[
"AF"];
6311 # return updated context 6312 $ContextVars = get_defined_vars();
6313 unset($ContextVars[
"_AF_File"]);
6314 return $ContextVars;
6323 private function FilterContext($Context, $ContextVars)
6325 # clear all variables if no setting for context is available 6326 # or setting is FALSE 6327 if (!isset($this->ContextFilters[$Context])
6328 || ($this->ContextFilters[$Context] == FALSE))
6332 # keep all variables if setting for context is TRUE 6333 elseif ($this->ContextFilters[$Context] == TRUE)
6335 return $ContextVars;
6339 $Prefixes = $this->ContextFilters[$Context];
6340 $FilterFunc =
function($VarName) use ($Prefixes) {
6341 foreach ($Prefixes as $Prefix)
6343 if (substr($VarName, $Prefix) === 0)
6350 return array_filter(
6351 $ContextVars, $FilterFunc, ARRAY_FILTER_USE_KEY);
6356 private $InterfaceDirList = array(
6357 "interface/%ACTIVEUI%/",
6358 "interface/%DEFAULTUI%/",
6364 private $IncludeDirList = array(
6365 "interface/%ACTIVEUI%/include/",
6366 "interface/%ACTIVEUI%/objects/",
6367 "interface/%DEFAULTUI%/include/",
6368 "interface/%DEFAULTUI%/objects/",
6371 private $ImageDirList = array(
6372 "interface/%ACTIVEUI%/images/",
6373 "interface/%DEFAULTUI%/images/",
6376 private $FunctionDirList = array(
6377 "interface/%ACTIVEUI%/include/",
6378 "interface/%DEFAULTUI%/include/",
6382 private $ExpandedDirectoryLists = array();
6384 const NOVALUE =
".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
6387 # ---- Page Caching (Internal Methods) ----------------------------------- 6394 private function CheckForCachedPage($PageName)
6396 # assume no cached page will be found 6399 # if returning a cached page is allowed 6400 if ($this->CacheCurrentPage)
6402 # get fingerprint for requested page 6403 $PageFingerprint = $this->GetPageFingerprint($PageName);
6405 # look for matching page in cache in database 6406 $this->DB->Query(
"SELECT * FROM AF_CachedPages" 6407 .
" WHERE Fingerprint = '".addslashes($PageFingerprint).
"'");
6409 # if matching page found 6410 if ($this->DB->NumRowsSelected())
6412 # if cached page has expired 6413 $Row = $this->DB->FetchRow();
6414 $ExpirationTime = strtotime(
6416 if (strtotime($Row[
"CachedAt"]) < $ExpirationTime)
6418 # clear expired pages from cache 6419 $ExpirationTimestamp = date(
"Y-m-d H:i:s", $ExpirationTime);
6420 $this->DB->Query(
"DELETE CP, CPTI FROM AF_CachedPages CP," 6421 .
" AF_CachedPageTagInts CPTI" 6422 .
" WHERE CP.CachedAt < '".$ExpirationTimestamp.
"'" 6423 .
" AND CPTI.CacheId = CP.CacheId");
6424 $this->DB->Query(
"DELETE FROM AF_CachedPages " 6425 .
" WHERE CachedAt < '".$ExpirationTimestamp.
"'");
6429 # display cached page and exit 6430 $CachedPage = $Row[
"PageContent"];
6435 # return any cached page found to caller 6444 private function UpdatePageCache($PageName, $PageContent)
6446 # if page caching is enabled and current page should be cached 6448 && $this->CacheCurrentPage
6449 && ($PageName !=
"404"))
6451 # if page content looks invalid 6452 if (strlen(trim(strip_tags($PageContent))) == 0)
6455 $LogMsg =
"Page not cached because content was empty." 6456 .
" (PAGE: ".$PageName.
", URL: ".$this->
FullUrl().
")";
6457 $this->
LogError(self::LOGLVL_ERROR, $LogMsg);
6461 # save page to cache 6462 $PageFingerprint = $this->GetPageFingerprint($PageName);
6463 $this->DB->Query(
"INSERT INTO AF_CachedPages" 6464 .
" (Fingerprint, PageContent) VALUES" 6465 .
" ('".$this->DB->EscapeString($PageFingerprint).
"', '" 6466 .$this->DB->EscapeString($PageContent).
"')");
6467 $CacheId = $this->DB->LastInsertId();
6469 # for each page cache tag that was added 6470 foreach ($this->PageCacheTags as $Tag => $Pages)
6472 # if current page is in list for tag 6473 if (in_array(
"CURRENT", $Pages) || in_array($PageName, $Pages))
6476 $TagId = $this->GetPageCacheTagId($Tag);
6478 # mark current page as associated with tag 6479 $this->DB->Query(
"INSERT INTO AF_CachedPageTagInts" 6480 .
" (TagId, CacheId) VALUES " 6481 .
" (".intval($TagId).
", ".intval($CacheId).
")");
6493 private function GetPageCacheTagId($Tag)
6495 # if tag is a non-negative integer 6496 if (is_numeric($Tag) && ($Tag > 0) && (intval($Tag) == $Tag))
6499 $Id = self::PAGECACHETAGIDOFFSET + $Tag;
6503 # look up ID in database 6504 $Id = $this->DB->Query(
"SELECT TagId FROM AF_CachedPageTags" 6505 .
" WHERE Tag = '".addslashes($Tag).
"'",
"TagId");
6507 # if ID was not found 6510 # add tag to database 6511 $this->DB->Query(
"INSERT INTO AF_CachedPageTags" 6512 .
" SET Tag = '".addslashes($Tag).
"'");
6513 $Id = $this->DB->LastInsertId();
6517 # return tag ID to caller 6526 private function GetPageFingerprint($PageName)
6528 # only get the environmental fingerprint once so that it is consistent 6529 # between page construction start and end 6530 static $EnvFingerprint;
6531 if (!isset($EnvFingerprint))
6533 $EnvData = json_encode($_GET).json_encode($_POST)
6534 .$_SERVER[
"SERVER_NAME"];
6535 $EnvFingerprint = md5($EnvData);
6538 # build page fingerprint and return it to caller 6539 return $PageName.
"-".$EnvFingerprint;
6548 private function UpdateLastUsedTimeForActiveSessions()
6552 $_SESSION[
"AF_SessionLastUsed"] = date(
"Y-m-d H:i:s");
6554 elseif (isset($_SESSION[
"AF_SessionLastUsed"]))
6556 unset($_SESSION[
"AF_SessionLastUsed"]);
const LOGLVL_ERROR
ERROR error logging level.
PageCacheEnabled($NewValue=DB_NOVALUE, $Persistent=FALSE)
Enable/disable page caching.
UnhookEvent($EventsOrEventName, $Callback=NULL, $Order=self::ORDER_MIDDLE)
Unhook one or more functions that were previously hooked to be called when the specified event is sig...
GetOrphanedTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently orphaned.
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.
ObjectLocationCacheExpirationInterval($NewInterval=DB_NOVALUE, $Persistent=FALSE)
Get/set object file location cache expiration period in minutes.
static GetScriptUrl()
Retrieve SCRIPT_URL server value, pulling it from elsewhere if that variable isn'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't have its o...
DoNotUrlFingerprint($Pattern)
Specify file or file name pattern to exclude from URL fingerprinting.
QueueUniqueTask($Callback, $Parameters=NULL, $Priority=self::PRIORITY_LOW, $Description="")
Add task to queue if not already in queue or currently running.
AddPrefixForAlternateDomain($Domain, $Prefix)
Add an alternate domain for the site which should map to a path tree under the main site URL...
static ConvertPhpIniSizeToBytes($Size)
Convert an abbreviated size from php.ini (e.g., 2g) to a number of bytes.
RequeueCurrentTask($NewValue=TRUE)
Set whether to requeue the currently-running background task when it completes.
const LOGLVL_FATAL
FATAL error logging level.
AddMetaTag($Attribs)
Add meta tag to page output.
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.
IsRunningInBackground()
Determine whether currently running inside a background task.
Abstraction for forum messages and resource comments.
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.
const CONTEXT_END
File loading context: page end file.
const PAGECACHETAGIDOFFSET
static PreferHttpHost($NewValue=NULL)
Get/set whether to prefer $_SERVER["HTTP_HOST"] (if available) over $_SERVER["SERVER_NAME"] when dete...
ClearObjectLocationCache()
Clear object (class) file location cache.
GetLock($LockName, $Wait=TRUE)
Get an exclusive ("write") lock on the specified name.
static FullUrl()
Get current full URL, before any clean URL remapping and with any query string (e.g.
Top-level framework for web applications.
static BaseUrl()
Get current base URL (the part before index.php) (e.g.
GetOrphanedTaskCount()
Retrieve current number of orphaned tasks.
SQL database abstraction object with smart query caching.
static ActiveUserInterface($UIName=NULL)
Get/set name of current active user interface.
SessionInUse($InUse=NULL)
Get/Set value of SessionInUse, which indicates if the current session is currently in use...
GetTaskQueueSize($Priority=NULL)
Retrieve current number of tasks in queue.
static SortCompare($A, $B)
Perform compare and return value appropriate for sort function callbacks.
static RootUrlOverride($NewValue=self::NOVALUE)
Get/set root URL override.
GetNextLowerBackgroundPriority($Priority=NULL)
Get next lower possible background task priority.
static UrlFingerprintingRewriteSupport()
Determine if rewrite support for URL fingerprinting is available.
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 level.
static ReachedViaAjax($NewSetting=NULL)
Determine if we were reached via an AJAX-based (or other automated) page load.
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't direc...
const EVENTTYPE_DEFAULT
Default event type.
GetPageUrl()
Get the full URL to the page.
static minify($js, $options=array())
Takes a string containing javascript and removes unneeded characters in order to shrink the code with...
const CONTEXT_ENV
File loading context: environmental include files.
TemplateLocationCacheExpirationInterval($NewInterval=DB_NOVALUE, $Persistent=FALSE)
Get/set UI template location cache expiration period in minutes.
RequireUIFile($FileNames, $Order=self::ORDER_MIDDLE)
Add file to list of required UI files.
SetBrowserCacheExpirationTime($MaxAge)
Set headers to control client-side caching of data served to the browser in this page load (usually J...
IsStaticOnlyEvent($EventName)
Report whether specified event only allows static callbacks.
const CONTEXT_PAGE
File loading context: PHP page file (from "pages").
PageCacheExpirationPeriod($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set page cache expiration period in seconds.
IsRegisteredEvent($EventName)
Check if event has been registered (is available to be signaled).
ReleaseLock($LockName)
Release lock with specified name.
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.
LogHighMemoryUsage($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether logging of high memory usage is enabled.
static ScssRewriteSupport()
Determine if SCSS rewrite support is available.
OnCrash()
Called automatically at program termination to ensure output is written out.
GetKnownPeriodicEvents()
Get list of known periodic events.
JavascriptMinimizationEnabled($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether the application framework will attempt to generate minimized JavaScript.
UseMinimizedJavascript($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether minimized JavaScript will be searched for and used if found.
const EVENTTYPE_CHAIN
Result chaining event type.
SlowPageLoadThreshold($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set how long a page load can take before it should be considered "slow" and may be logged...
AddMetaTagOnce($Attribs, $UniqueAttribs=NULL)
Add meta tag to page output if not already present.
ClearPageCacheForTag($Tag)
Clear all cached pages associated with specified tag.
DoNotCacheCurrentPage()
Prevent the current page from being cached.
SuppressStandardPageStartAndEnd($NewSetting=TRUE)
Suppress loading of standard page start and end files.
SCSS compiler written in PHP.
GetSecondsBeforeTimeout()
Get remaining available (PHP) execution time.
TaskIsInQueue($Callback, $Parameters=NULL)
Check if task is already in queue or currently running.
GetCurrentBackgroundPriority()
Determine current priority if running in background.
AddFunctionDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for function ("F-") files.
GetAlternateDomains()
Get the list of configured alternate domains.
static ArrayPermutations($Items, $Perms=array())
Return all possible permutations of a given array.
AddPageCacheTag($Tag, $Pages=NULL)
Add caching tag for current page or specified pages.
GetPageCacheInfo()
Get page cache information.
const ORDER_MIDDLE
Handle item after ORDER_FIRST and before ORDER_LAST items.
const OUTPUT_MODIFICATION_THRESHOLD
Threshold below which page output modifications are considered to have failed.
RegisterEvent($EventsOrEventName, $EventType=NULL)
Register one or more events that may be signaled.
UrlFingerprintingEnabled($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether URL fingerprinting is enabled.
static JsMinRewriteSupport()
Determine if rewrite support for JavaScript minification is available.
DownloadFile($FilePath, $FileName=NULL, $MimeType=NULL)
Send specified file for download by user.
GetPageName()
Get name of page being loaded.
LogSlowPageLoads($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether logging of long page load times is enabled.
CleanUrlIsMapped($Path)
Report whether clean URL has already been mapped.
HighMemoryUsageThreshold($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set what percentage of max memory (set via the memory_limit PHP configuration directive) a page l...
SetJumpToPage($Page, $Delay=0, $IsLiteral=FALSE)
Set URL of page to autoload after PHP page file is executed.
const CONTEXT_START
File loading context: page start file.
const CONTEXT_COMMON
File loading context: common HTML files.
const PRIORITY_HIGH
Highest priority.
LoadFunction($Callback)
Attempt to load code for function or method if not currently available.
GetNextHigherBackgroundPriority($Priority=NULL)
Get next higher possible background task priority.
GetRunningTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently running.
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.
static DefaultUserInterface($UIName=NULL)
Get/set name of current default user interface.
HookEvent($EventsOrEventName, $Callback=NULL, $Order=self::ORDER_MIDDLE)
Hook one or more functions to be called when the specified event is signaled.
ClearPageCache()
Clear all pages from page cache.
IsHookedEvent($EventName)
Check if an event is registered and is hooked to.
static GetFreeMemory()
Get current amount of free memory.
const FT_JAVASCRIPT
JavaScript file type.
const CONTEXT_INTERFACE
File loading context: HTML interface file.
AddImageDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for image files.
const FT_CSS
CSS file type.
HtmlCharset($NewSetting=NULL)
Get/set HTTP character encoding value.
const ORDER_FIRST
Handle item first (i.e.
const SQL_DATE_FORMAT
Format to feed to date() to get SQL-compatible date/time string.
static GetPhpMaxUploadSize()
Get the maximum size for a file upload in bytes.
IncludeUIFile($FileNames, $AdditionalAttributes=NULL)
Search UI directories for specified JavaScript or CSS file and print HTML tag to load file...
GetUncleanUrlForPath($Path)
Get the unclean URL for mapped for a path.
const FT_IMAGE
Image (GIF/JPG/PNG) file type.
ClearTemplateLocationCache()
Clear template location cache.
GetPrefixForAlternateDomain($Domain)
Get configured prefix for an alternate domain, if one exists.
static GetFileType($FileName)
Determine type of specified file based on the file name.
GetUncleanUrl()
Get the unclean URL for the current page.
DoNotMinimizeFile($File)
Specify file(s) to not attempt to minimize.
RecordContextInCaseOfCrash($BacktraceOptions=0, $BacktraceLimit=0)
Record the current execution context in case of crash.
JumpToPageIsSet()
Report whether a page to autoload has been set.
const ORDER_LAST
Handle item last (i.e.
LoggingLevel($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set logging level.
UseBaseTag($NewValue=NULL)
Get/set whether or not to use the "base" tag to ensure relative URL paths are correct.
static HtaccessSupport()
Determine if .htaccess files are enabled.
GetTask($TaskId)
Retrieve task info from queue (either running or queued tasks).
MaxExecutionTime($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set maximum PHP execution time.
static GetPhpMemoryLimit()
Get PHP memory limit in bytes.
SetContextFilter($Context, $NewSetting)
Configure filtering of variables left in the execution environment for the next loaded file after a P...
LoadPage($PageName)
Load page PHP and HTML/TPL files.
GetUserInterfacePaths($FilterExp=NULL)
Get list of available user interfaces and the relative paths to the base directory for each interface...
AddEnvInclude($FileName)
Add file to be included to set up environment.
GetCleanUrlList()
Get list of all clean URLs currently added.
GetUserInterfaces($FilterExp=NULL)
Get list of available user interfaces and their labels.
ReQueueOrphanedTask($TaskId, $NewPriority=NULL)
Move orphaned task back into queue.
GetLogEntries($Limit=0)
Get log entries, in reverse chronological order.
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.
TaskExecutionEnabled($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether automatic task execution is enabled.
static GetTaskCallbackSynopsis($TaskInfo)
Get printable synopsis for task callback.
PUIFile($FileName)
Search UI directories for specified interface (image, CSS, JavaScript etc) file and print name of cor...
GenerateCompactCss($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether generating compact CSS (when compiling SCSS) is enabled.
const LOGFILE_MAX_LINE_LENGTH
Maximum length for a line in the log file.
GetPageLocation()
Get the URL path to the page without the base path, if present.
ScssSupportEnabled($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether SCSS compilation support is enabled.
BeginAjaxResponse($ResponseType="JSON", $CloseSession=TRUE)
Begin an AJAX response, setting the necessary HTTP headers and optionally closing the PHP session...
MaxTasks($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set maximum number of tasks to have running simultaneously.
GetElapsedExecutionTime()
Get time elapsed since constructor was called.
static RunPeriodicEvent($EventName, $Callback, $Parameters)
Run periodic event and then save info needed to know when to run it again.
SetBrowserDetectionFunc($DetectionFunc)
Specify function to use to detect the web browser type.
static RootUrl()
Get portion of current URL through host name, with no trailing slash (e.g.
AddCleanUrl($Pattern, $Page, $GetVars=NULL, $Template=NULL)
Add clean URL mapping.
static GetCallerInfo($Element=NULL)
Get info about call to current function.
const PRIORITY_BACKGROUND
Lowest priority.
LogMessage($Level, $Msg)
Write status message to log.
const FT_OTHER
File type other than CSS, image, or JavaScript.
static AddObjectDirectory($Dir, $NamespacePrefixes=array(), $Callback=NULL)
Add directory to be searched for object files when autoloading.