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 # adjust environment in case we are being run via CGI 31 $this->AdjustEnvironmentForCgi();
33 # make sure default time zone is set 34 # (using CST if nothing set because we have to use something 35 # and Scout is based in Madison, WI, which is in CST) 36 if ((ini_get(
"date.timezone") === NULL)
37 || !strlen(ini_get(
"date.timezone")))
39 ini_set(
"date.timezone",
"America/Chicago");
42 # save execution start time 43 $this->ExecutionStartTime = microtime(TRUE);
45 # set up default object file search locations 46 self::AddObjectDirectory(
"local/interface/%ACTIVEUI%/objects");
47 self::AddObjectDirectory(
"interface/%ACTIVEUI%/objects");
48 self::AddObjectDirectory(
"local/interface/%DEFAULTUI%/objects");
49 self::AddObjectDirectory(
"interface/%DEFAULTUI%/objects");
50 self::AddObjectDirectory(
"local/objects");
51 self::AddObjectDirectory(
"objects");
53 # set up object file autoloader 54 spl_autoload_register(array($this,
"AutoloadObjects"));
56 # set up function to output any buffered text in case of crash 57 register_shutdown_function(array($this,
"OnCrash"));
59 # if we were not invoked via command line interface 60 # and session initialization has not been explicitly suppressed 61 if ((php_sapi_name() !==
"cli") && (!self::$SuppressSessionInitialization))
63 # build cookie domain string 64 $SessionDomain = isset($_SERVER[
"SERVER_NAME"]) ? $_SERVER[
"SERVER_NAME"]
65 : isset($_SERVER[
"HTTP_HOST"]) ? $_SERVER[
"HTTP_HOST"]
68 # include a leading period so that older browsers implementing 69 # rfc2109 do not reject our cookie 70 $SessionDomain =
".".$SessionDomain;
72 # if it appears our session storage area is writable 73 if (is_writable(session_save_path()))
75 # store our session files in a subdirectory to avoid 76 # accidentally sharing sessions with other installations 78 $SessionStorage = session_save_path()
79 .
"/".self::$AppName.
"_".md5($SessionDomain.dirname(__FILE__));
81 # create session storage subdirectory if not found 82 if (!is_dir($SessionStorage)) { mkdir($SessionStorage, 0700 ); }
84 # if session storage subdirectory is writable 85 if (is_writable($SessionStorage))
87 # save parameters of our session storage as instance variables 89 $this->SessionGcProbability =
90 ini_get(
"session.gc_probability") / ini_get(
"session.gc_divisor");
91 # require a gc probability of at least MIN_GC_PROBABILITY 92 if ($this->SessionGcProbability < self::MIN_GC_PROBABILITY)
94 $this->SessionGcProbability = self::MIN_GC_PROBABILITY;
97 $this->SessionStorage = $SessionStorage;
99 # set the new session storage location 100 session_save_path($SessionStorage);
102 # disable PHP's garbage collection, as it does not handle 103 # subdirectories (instead, we'll do the cleanup as we run 105 ini_set(
"session.gc_probability", 0);
109 # set garbage collection max period to our session lifetime 110 ini_set(
"session.gc_maxlifetime", self::$SessionLifetime);
112 # limit cookie to secure connection if we are running over same 113 $SecureCookie = isset($_SERVER[
"HTTPS"]) ? TRUE : FALSE;
115 # Cookies lacking embedded dots are... fun. 116 # rfc2109 sec 4.3.2 says to reject them 117 # rfc2965 sec 3.3.2 says to reject them 118 # rfc6265 sec 4.1.2.3 says only that "public suffixes" 119 # should be rejected. They reference Mozilla's 120 # publicsuffix.org, which does not contain 'localhost'. 121 # However, empirically in early 2017 Firefox still rejects 123 # Therefore, don't set a cookie domain if we're running on 124 # localhost to avoid this problem. 125 if (preg_match(
'/^\.localhost(:[0-9]+)?$/', $SessionDomain))
129 session_set_cookie_params(self::$SessionLifetime,
"/",
130 $SessionDomain, $SecureCookie, TRUE);
132 # attempt to start session 133 $SessionStarted = @session_start();
135 # if session start failed 136 if (!$SessionStarted)
138 # regenerate session ID and attempt to start session again 139 session_regenerate_id(TRUE);
143 # bump up our cookie expiry time, so that it'll die 144 # $SessionLifetime from now, rather than $SessionLifetime 145 # from whenever we created it 147 session_name(), session_id(),
148 time() + self::$SessionLifetime,
"/",
149 $SessionDomain, $SecureCookie, TRUE);
152 # set up our internal environment 155 # set up our exception handler 156 set_exception_handler(array($this,
"GlobalExceptionHandler"));
158 # load our settings from database 159 $this->LoadSettings();
161 # set PHP maximum execution time 162 ini_set(
"max_execution_time", $this->Settings[
"MaxExecTime"]);
163 set_time_limit($this->Settings[
"MaxExecTime"]);
165 # set database slow query threshold for foreground execution 167 self::MIN_DB_SLOW_QUERY_THRESHOLD,
168 self::DatabaseSlowQueryThresholdForForeground()));
170 # register events we handle internally 174 # attempt to create SCSS cache directory if needed and it does not exist 176 { @mkdir(self::$ScssCacheDir, 0777, TRUE); }
178 # attempt to create minimized JS cache directory if needed and it does not exist 181 && !is_dir(self::$JSMinCacheDir))
183 @mkdir(self::$JSMinCacheDir, 0777, TRUE);
186 # initialize task manager 195 public function __destruct()
197 # if template location cache is flagged to be saved 198 if ($this->SaveTemplateLocationCache)
200 # write template location cache out and update cache expiration 201 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings" 202 .
" SET TemplateLocationCache = '" 203 .addslashes(serialize(
204 $this->TemplateLocationCache)).
"'," 205 .
" TemplateLocationCacheExpiration = '" 207 $this->TemplateLocationCacheExpiration).
"'");
210 # if object location cache is flagged to be saved 211 if (self::$SaveObjectLocationCache)
213 # write object location cache out and update cache expiration 214 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings" 215 .
" SET ObjectLocationCache = '" 216 .addslashes(serialize(
217 self::$ObjectLocationCache)).
"'," 218 .
" ObjectLocationCacheExpiration = '" 220 self::$ObjectLocationCacheExpiration).
"'");
230 public function GlobalExceptionHandler($Exception)
232 # display exception info 233 $Message = $Exception->getMessage();
234 $Location = str_replace(getcwd().
"/",
"",
235 $Exception->getFile().
"[".$Exception->getLine().
"]");
236 $Trace = preg_replace(
":(#[0-9]+) ".getcwd().
"/".
":",
"$1 ",
237 $Exception->getTraceAsString());
238 if (php_sapi_name() ==
"cli")
240 print
"Uncaught Exception\n" 241 .
"Message: ".$Message.
"\n" 242 .
"Location: ".$Location.
"\n" 248 ?><table width=
"100%" cellpadding=
"5" 249 style=
"border: 2px solid #666666; background: #CCCCCC; 250 font-family: Courier New, Courier, monospace; 251 margin-top: 10px;"><tr><td>
252 <div style=
"color: #666666;">
253 <span style=
"font-size: 150%;">
254 <b>Uncaught Exception</b></span><br />
255 <b>
Message:</b> <i><?= $Message ?></i><br />
256 <b>Location:</b> <i><?= $Location ?></i><br />
257 <b>Trace:</b> <blockquote><pre><?= $Trace ?></pre></blockquote>
259 </td></tr></table><?
PHP 262 # log exception if not running from command line 263 if (php_sapi_name() !==
"cli")
265 $TraceString = $Exception->getTraceAsString();
266 $TraceString = str_replace(
"\n",
", ", $TraceString);
267 $TraceString = preg_replace(
":(#[0-9]+) ".getcwd().
"/".
":",
268 "$1 ", $TraceString);
269 $LogMsg =
"Uncaught exception (".$Exception->getMessage().
")" 270 .
" at ".$Location.
"." 271 .
" TRACE: ".$TraceString
273 $this->
LogError(self::LOGLVL_ERROR, $LogMsg);
297 $Dir, $NamespacePrefixes = array(), $Callback = NULL)
299 # check to make sure any supplied callback looks valid 300 if (!is_null($Callback) && !is_callable($Callback))
302 throw new InvalidArgumentException(
"Supplied callback (\"" 303 .$Callback.
"\") is invalid.");
306 # make sure directory has trailing slash 307 $Dir = $Dir.((substr($Dir, -1) !=
"/") ?
"/" :
"");
309 # make sure namespaces is an array 310 if (!is_array($NamespacePrefixes))
312 if (is_string($NamespacePrefixes))
314 $NamespacePrefixes = [ $NamespacePrefixes ];
318 throw new InvalidArgumentException(
"Supplied namespace (\"" 319 .$NamespacePrefixes.
"\") is invalid.");
323 # make sure namespace prefixes are in decreasing order of length 324 usort($NamespacePrefixes,
function($A, $B)
326 return strlen($B) - strlen($A);
329 # add directory to directory list 330 self::$ObjectDirectories[$Dir] = array(
331 "NamespacePrefixes" => $NamespacePrefixes,
332 "Callback" => $Callback,
357 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
359 # add directories to existing image directory list 360 $this->ImageDirList = $this->AddToDirList(
361 $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
386 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
388 # add directories to existing image directory list 389 $this->IncludeDirList = $this->AddToDirList(
390 $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
414 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
416 # add directories to existing image directory list 417 $this->InterfaceDirList = $this->AddToDirList(
418 $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
420 # cleared cached lists for user interfaces 421 self::$UserInterfaceListCache = array();
422 self::$UserInterfacePathsCache = array();
446 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
448 # add directories to existing image directory list 449 $this->FunctionDirList = $this->AddToDirList(
450 $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
460 $this->BrowserDetectFunc = $DetectionFunc;
471 if (is_callable($Callback))
473 $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
487 $NewInterval =
DB_NOVALUE, $Persistent = FALSE)
489 return $this->UpdateSetting(
490 "TemplateLocationCacheInterval", $NewInterval, $Persistent);
498 $this->TemplateLocationCache = array();
499 $this->SaveTemplateLocationCache = TRUE;
514 return $this->UpdateSetting(
515 "ObjectLocationCacheInterval", $NewValue, $Persistent);
523 self::$ObjectLocationCache = array();
524 self::$SaveObjectLocationCache = TRUE;
539 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
555 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
572 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
589 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
606 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
623 $BacktraceOptions = 0, $BacktraceLimit = 0)
625 if (version_compare(PHP_VERSION,
"5.4.0",
">="))
627 $this->SavedContext = debug_backtrace(
628 $BacktraceOptions, $BacktraceLimit);
632 $this->SavedContext = debug_backtrace($BacktraceOptions);
634 array_shift($this->SavedContext);
643 # perform any clean URL rewriting 644 $PageName = $this->RewriteCleanUrls($PageName);
646 # sanitize incoming page name and save local copy 647 $PageName = preg_replace(
"/[^a-zA-Z0-9_.-]/",
"", $PageName);
648 $this->PageName = $PageName;
650 # if page caching is turned on 653 # if we have a cached page 654 $CachedPage = $this->CheckForCachedPage($PageName);
655 if ($CachedPage !== NULL)
657 # set header to indicate cache hit was found 658 header(
"X-ScoutAF-Cache: HIT");
660 # display cached page and exit 666 # set header to indicate no cache hit was found 667 header(
"X-ScoutAF-Cache: MISS");
671 # buffer any output from includes or PHP file 674 # include any files needed to set up execution environment 675 $IncludeFileContext = array();
676 foreach ($this->EnvIncludes as $IncludeFile)
678 $IncludeFileContext = $this->FilterContext(self::CONTEXT_ENV,
679 self::IncludeFile($IncludeFile, $IncludeFileContext));
683 $this->
SignalEvent(
"EVENT_PAGE_LOAD", array(
"PageName" => $PageName));
685 # signal PHP file load 686 $SignalResult = $this->
SignalEvent(
"EVENT_PHP_FILE_LOAD", array(
687 "PageName" => $PageName));
689 # if signal handler returned new page name value 690 $NewPageName = $PageName;
691 if (($SignalResult[
"PageName"] != $PageName)
692 && strlen($SignalResult[
"PageName"]))
694 # if new page name value is page file 695 if (file_exists($SignalResult[
"PageName"]))
697 # use new value for PHP file name 698 $PageFile = $SignalResult[
"PageName"];
702 # use new value for page name 703 $NewPageName = $SignalResult[
"PageName"];
706 # update local copy of page name 707 $this->PageName = $NewPageName;
710 # if we do not already have a PHP file 711 if (!isset($PageFile))
713 # look for PHP file for page 714 $OurPageFile =
"pages/".$NewPageName.
".php";
715 $LocalPageFile =
"local/pages/".$NewPageName.
".php";
716 $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
717 : (file_exists($OurPageFile) ? $OurPageFile
718 :
"pages/".$this->DefaultPage.
".php");
722 $IncludeFileContext = $this->FilterContext(self::CONTEXT_PAGE,
723 self::IncludeFile($PageFile, $IncludeFileContext));
725 # save buffered output to be displayed later after HTML file loads 726 $PageOutput = ob_get_contents();
729 # signal PHP file load is complete 731 $Context[
"Variables"] = $IncludeFileContext;
733 array(
"PageName" => $PageName,
"Context" => $Context));
734 $PageCompleteOutput = ob_get_contents();
737 # set up for possible TSR (Terminate and Stay Resident :)) 738 $ShouldTSR = $this->PrepForTSR();
740 # if PHP file indicated we should autorefresh to somewhere else 741 if (($this->JumpToPage) && ($this->JumpToPageDelay == 0))
744 if (!strlen(trim($PageOutput)))
746 # if client supports HTTP/1.1, use a 303 as it is most accurate 747 if ($_SERVER[
"SERVER_PROTOCOL"] ==
"HTTP/1.1")
749 header(
"HTTP/1.1 303 See Other");
750 header(
"Location: ".$this->JumpToPage);
754 # if the request was an HTTP/1.0 GET or HEAD, then 755 # use a 302 response code. 757 # NB: both RFC 2616 (HTTP/1.1) and RFC1945 (HTTP/1.0) 758 # explicitly prohibit automatic redirection via a 302 759 # if the request was not GET or HEAD. 760 if ($_SERVER[
"SERVER_PROTOCOL"] ==
"HTTP/1.0" &&
761 ($_SERVER[
"REQUEST_METHOD"] ==
"GET" ||
762 $_SERVER[
"REQUEST_METHOD"] ==
"HEAD") )
764 header(
"HTTP/1.0 302 Found");
765 header(
"Location: ".$this->JumpToPage);
768 # otherwise, fall back to a meta refresh 771 print
'<html><head><meta http-equiv="refresh" ' 772 .
'content="0; URL='.$this->JumpToPage.
'">' 773 .
'</head><body></body></html>';
778 # else if HTML loading is not suppressed 779 elseif (!$this->SuppressHTML)
781 # set content-type to avoid diacritic errors 782 header(
"Content-Type: text/html; charset=" 785 # load common HTML file (defines common functions) if available 786 $CommonHtmlFile = $this->FindFile($this->IncludeDirList,
787 "Common", array(
"tpl",
"html"));
790 $IncludeFileContext = $this->FilterContext(self::CONTEXT_COMMON,
791 self::IncludeFile($CommonHtmlFile, $IncludeFileContext));
795 $this->LoadUIFunctions();
797 # begin buffering content 800 # signal HTML file load 801 $SignalResult = $this->
SignalEvent(
"EVENT_HTML_FILE_LOAD", array(
802 "PageName" => $PageName));
804 # if signal handler returned new page name value 805 $NewPageName = $PageName;
806 $PageContentFile = NULL;
807 if (($SignalResult[
"PageName"] != $PageName)
808 && strlen($SignalResult[
"PageName"]))
810 # if new page name value is HTML file 811 if (file_exists($SignalResult[
"PageName"]))
813 # use new value for HTML file name 814 $PageContentFile = $SignalResult[
"PageName"];
818 # use new value for page name 819 $NewPageName = $SignalResult[
"PageName"];
823 # load page content HTML file if available 824 if ($PageContentFile === NULL)
826 $PageContentFile = $this->FindFile(
827 $this->InterfaceDirList, $NewPageName,
828 array(
"tpl",
"html"));
830 if ($PageContentFile)
832 $IncludeFileContext = $this->FilterContext(self::CONTEXT_INTERFACE,
833 self::IncludeFile($PageContentFile, $IncludeFileContext));
837 print
"<h2>ERROR: No HTML/TPL template found" 838 .
" for this page (".$NewPageName.
").</h2>";
842 # signal HTML file load complete 843 $SignalResult = $this->
SignalEvent(
"EVENT_HTML_FILE_LOAD_COMPLETE");
845 # stop buffering and save output 846 $PageContentOutput = ob_get_contents();
849 # if standard page start/end have not been suppressed 850 $PageStartOutput =
"";
852 if (!$this->SuppressStdPageStartAndEnd)
854 # load page start HTML file if available 855 $PageStartFile = $this->FindFile($this->IncludeDirList,
"Start",
856 array(
"tpl",
"html"), array(
"StdPage",
"StandardPage"));
860 $IncludeFileContext = self::IncludeFile(
861 $PageStartFile, $IncludeFileContext);
862 $PageStartOutput = ob_get_contents();
865 $IncludeFileContext = $this->FilterContext(
866 self::CONTEXT_START, $IncludeFileContext);
868 # load page end HTML file if available 869 $PageEndFile = $this->FindFile($this->IncludeDirList,
"End",
870 array(
"tpl",
"html"), array(
"StdPage",
"StandardPage"));
874 self::IncludeFile($PageEndFile, $IncludeFileContext);
875 $PageEndOutput = ob_get_contents();
880 # clear include file context because it may be large and is no longer needed 881 unset($IncludeFileContext);
883 # if page auto-refresh requested 884 if ($this->JumpToPage)
886 # add auto-refresh tag to page 888 "http-equiv" =>
"refresh",
889 "content" => $this->JumpToPageDelay,
890 "url" => $this->JumpToPage,
895 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
897 # get list of any required files not loaded 898 $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
900 # add file loading tags to page 901 $FullPageOutput = $this->AddFileTagsToPageOutput(
902 $FullPageOutput, $RequiredFiles);
904 # add any requested meta tags to page 905 $FullPageOutput = $this->AddMetaTagsToPageOutput($FullPageOutput);
907 # perform any regular expression replacements in output 908 $NewFullPageOutput = preg_replace($this->OutputModificationPatterns,
909 $this->OutputModificationReplacements, $FullPageOutput);
911 # check to make sure replacements didn't fail 912 $FullPageOutput = $this->CheckOutputModification(
913 $FullPageOutput, $NewFullPageOutput,
914 "regular expression replacements");
916 # for each registered output modification callback 917 foreach ($this->OutputModificationCallbacks as $Info)
919 # set up data for callback 920 $this->OutputModificationCallbackInfo = $Info;
922 # perform output modification 923 $NewFullPageOutput = preg_replace_callback($Info[
"SearchPattern"],
924 array($this,
"OutputModificationCallbackShell"),
927 # check to make sure modification didn't fail 928 $ErrorInfo =
"callback info: ".print_r($Info, TRUE);
929 $FullPageOutput = $this->CheckOutputModification(
930 $FullPageOutput, $NewFullPageOutput, $ErrorInfo);
933 # provide the opportunity to modify full page output 934 $SignalResult = $this->
SignalEvent(
"EVENT_PAGE_OUTPUT_FILTER", array(
935 "PageOutput" => $FullPageOutput));
936 if (isset($SignalResult[
"PageOutput"])
937 && strlen(trim($SignalResult[
"PageOutput"])))
939 $FullPageOutput = $SignalResult[
"PageOutput"];
942 # if relative paths may not work because we were invoked via clean URL 943 if ($this->CleanUrlRewritePerformed || self::WasUrlRewritten())
945 # if using the <base> tag is okay 949 # add <base> tag to header 950 $PageStartOutput = str_replace(
"<head>",
951 "<head><base href=\"".$BaseUrl.
"\" />",
954 # re-assemble full page with new header 955 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
957 # the absolute URL to the current page 960 # make HREF attribute values with just a fragment ID 961 # absolute since they don't work with the <base> tag because 962 # they are relative to the current page/URL, not the site 964 $NewFullPageOutput = preg_replace(
965 array(
"%href=\"(#[^:\" ]+)\"%i",
"%href='(#[^:' ]+)'%i"),
966 array(
"href=\"".$FullUrl.
"$1\"",
"href='".$FullUrl.
"$1'"),
969 # check to make sure HREF cleanup didn't fail 970 $FullPageOutput = $this->CheckOutputModification(
971 $FullPageOutput, $NewFullPageOutput,
976 # try to fix any relative paths throughout code 977 $SrcFileExtensions =
"(js|css|gif|png|jpg|svg|ico)";
978 $RelativePathPatterns = array(
979 "%src=\"/?([^?*:;{}\\\\\" ]+)\.".$SrcFileExtensions.
"\"%i",
980 "%src='/?([^?*:;{}\\\\' ]+)\.".$SrcFileExtensions.
"'%i",
981 # don
't rewrite HREF attributes that are just 982 # fragment IDs because they are relative to the 983 # current page/URL, not the site root 984 "%href=\"/?([^#][^:\" ]*)\"%i", 985 "%href='/?([^#][^:
' ]*)'%i
", 986 "%action=\
"/?([^#][^:\" ]*)\"%i",
987 "%action='/?([^#][^:' ]*)'%i",
988 "%@import\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
989 "%@import\s+url\('/?([^:\" ]+)'\s*\)%i",
990 "%src:\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
991 "%src:\s+url\('/?([^:\" ]+)'\s*\)%i",
992 "%@import\s+\"/?([^:\" ]+)\"\s*%i",
993 "%@import\s+'/?([^:\" ]+)'\s*%i",
995 $RelativePathReplacements = array(
996 "src=\"".$BaseUrl.
"$1.$2\"",
997 "src=\"".$BaseUrl.
"$1.$2\"",
998 "href=\"".$BaseUrl.
"$1\"",
999 "href=\"".$BaseUrl.
"$1\"",
1000 "action=\"".$BaseUrl.
"$1\"",
1001 "action=\"".$BaseUrl.
"$1\"",
1002 "@import url(\"".$BaseUrl.
"$1\")",
1003 "@import url('".$BaseUrl.
"$1')",
1004 "src: url(\"".$BaseUrl.
"$1\")",
1005 "src: url('".$BaseUrl.
"$1')",
1006 "@import \"".$BaseUrl.
"$1\"",
1007 "@import '".$BaseUrl.
"$1'",
1009 $NewFullPageOutput = preg_replace($RelativePathPatterns,
1010 $RelativePathReplacements, $FullPageOutput);
1012 # check to make sure relative path fixes didn't fail 1013 $FullPageOutput = $this->CheckOutputModification(
1014 $FullPageOutput, $NewFullPageOutput,
1015 "relative path fixes");
1019 # handle any necessary alternate domain rewriting 1020 $FullPageOutput = $this->RewriteAlternateDomainUrls($FullPageOutput);
1022 # update page cache for this page 1023 $this->UpdatePageCache($PageName, $FullPageOutput);
1025 # write out full page 1026 print $FullPageOutput;
1029 # run any post-processing routines 1030 foreach ($this->PostProcessingFuncs as $Func)
1032 call_user_func_array($Func[
"FunctionName"], $Func[
"Arguments"]);
1035 # write out any output buffered from page code execution 1036 if (strlen($PageOutput))
1038 if (!$this->SuppressHTML)
1040 ?><table width=
"100%" cellpadding=
"5" 1041 style=
"border: 2px solid #666666; background: #CCCCCC; 1042 font-family: Courier New, Courier, monospace; 1043 margin-top: 10px;"><tr><td><?
PHP 1045 if ($this->JumpToPage)
1047 ?><div style=
"color: #666666;"><span style=
"font-size: 150%;">
1048 <b>Page Jump Aborted</b></span>
1049 (because of error or other unexpected output)<br />
1051 <i><?
PHP print($this->JumpToPage); ?></i></div><?
PHP 1054 if (!$this->SuppressHTML)
1056 ?></td></tr></table><?
PHP 1060 # write out any output buffered from the page code execution complete signal 1061 if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
1063 print $PageCompleteOutput;
1066 # log slow page loads 1068 && !$this->DoNotLogSlowPageLoad
1072 $RemoteHost = gethostbyaddr($_SERVER[
"REMOTE_ADDR"]);
1073 if ($RemoteHost === FALSE)
1075 $RemoteHost = $_SERVER[
"REMOTE_ADDR"];
1077 elseif ($RemoteHost != $_SERVER[
"REMOTE_ADDR"])
1079 $RemoteHost .=
" (".$_SERVER[
"REMOTE_ADDR"].
")";
1081 $SlowPageLoadMsg =
"Slow page load (" 1083 .$this->FullUrl().
" from ".$RemoteHost;
1084 $this->
LogMessage(self::LOGLVL_INFO, $SlowPageLoadMsg);
1087 # execute callbacks that should not have their output buffered 1088 foreach ($this->UnbufferedCallbacks as $Callback)
1090 call_user_func_array($Callback[0], $Callback[1]);
1093 # log high memory usage 1094 if (function_exists(
"memory_get_peak_usage"))
1099 && (memory_get_peak_usage(TRUE) >= $MemoryThreshold))
1101 $HighMemUsageMsg =
"High peak memory usage (" 1102 .number_format(memory_get_peak_usage(TRUE)).
") for " 1103 .$this->FullUrl().
" from " 1104 .$_SERVER[
"REMOTE_ADDR"];
1105 $this->
LogMessage(self::LOGLVL_INFO, $HighMemUsageMsg);
1109 $this->UpdateLastUsedTimeForActiveSessions();
1111 # terminate and stay resident (TSR!) if indicated and HTML has been output 1112 # (only TSR if HTML has been output because otherwise browsers will misbehave) 1113 if ($ShouldTSR) { $this->LaunchTSR(); }
1122 return $this->RunningInBackground;
1132 return $this->PageName;
1142 # retrieve current URL 1143 $Url = self::GetScriptUrl();
1145 # remove the base path if present 1146 $BasePath = $this->Settings[
"BasePath"];
1147 if (stripos($Url, $BasePath) === 0)
1149 $Url = substr($Url, strlen($BasePath));
1152 # if we're being accessed via an alternate domain, 1153 # add the appropriate prefix in 1155 self::$RootUrlOverride !== NULL)
1157 $VHost = $_SERVER[
"SERVER_NAME"];
1158 if (isset($this->AlternateDomainPrefixes[$VHost]))
1160 $ThisPrefix = $this->AlternateDomainPrefixes[$VHost];
1161 $Url = $ThisPrefix.
"/".$Url;
1175 return self::BaseUrl().$this->GetPageLocation();
1194 && (strpos($Page,
"?") === FALSE)
1195 && ((strpos($Page,
"=") !== FALSE)
1196 || ((stripos($Page,
".php") === FALSE)
1197 && (stripos($Page,
".htm") === FALSE)
1198 && (strpos($Page,
"/") === FALSE)))
1199 && (stripos($Page,
"http://") !== 0)
1200 && (stripos($Page,
"https://") !== 0))
1202 $this->JumpToPage = self::BaseUrl() .
"index.php?P=".$Page;
1206 $this->JumpToPage = $Page;
1208 $this->JumpToPageDelay = $Delay;
1217 return ($this->JumpToPage === NULL) ? FALSE : TRUE;
1231 if ($NewSetting !== NULL) { $this->
HtmlCharset = $NewSetting; }
1232 return $this->HtmlCharset;
1246 if (!is_array($File)) { $File = array($File); }
1247 $this->DoNotMinimizeList = array_merge($this->DoNotMinimizeList, $File);
1262 if ($NewValue !== NULL) { $this->
UseBaseTag = $NewValue ? TRUE : FALSE; }
1263 return $this->UseBaseTag;
1275 $this->SuppressHTML = $NewSetting;
1287 $this->SuppressStdPageStartAndEnd = $NewSetting;
1297 if ($NewValue !== NULL)
1299 self::$SuppressSessionInitialization = $NewValue;
1301 return self::$SuppressSessionInitialization;
1311 if ($UIName !== NULL)
1313 self::$DefaultUI = $UIName;
1315 return self::$DefaultUI;
1326 if ($UIName !== NULL)
1328 self::$ActiveUI = preg_replace(
"/^SPTUI--/",
"", $UIName);
1330 return self::$ActiveUI;
1345 if (!isset(self::$UserInterfaceListCache[$FilterExp]))
1347 # retrieve paths to user interface directories 1350 # start out with an empty list 1351 self::$UserInterfaceListCache[$FilterExp] = array();
1353 # for each possible UI directory 1354 foreach ($Paths as $CanonicalName => $Path)
1356 # if name file available 1357 $LabelFile = $Path.
"/NAME";
1358 if (is_readable($LabelFile))
1361 $Label = file_get_contents($LabelFile);
1363 # if the UI name looks reasonable 1364 if (strlen(trim($Label)))
1367 self::$UserInterfaceListCache[$FilterExp][$CanonicalName] =
1372 # if we do not have a name yet 1373 if (!isset(self::$UserInterfaceListCache[$FilterExp][$CanonicalName]))
1375 # use base directory for name 1376 self::$UserInterfaceListCache[$FilterExp][$CanonicalName] =
1382 # return list to caller 1383 return self::$UserInterfaceListCache[$FilterExp];
1396 if (!isset(self::$UserInterfacePathsCache[$FilterExp]))
1398 # extract possible UI directories from interface directory list 1399 $InterfaceDirs = array();
1400 foreach ($this->ExpandDirectoryList($this->InterfaceDirList) as $Dir)
1403 if (preg_match(
"#([a-zA-Z0-9/]*interface)/[a-zA-Z0-9%/]*#",
1407 if (!in_array($Dir, $InterfaceDirs))
1409 $InterfaceDirs[] = $Dir;
1414 # reverse order of interface directories so that the directory 1415 # returned is the base directory for the interface 1416 $InterfaceDirs = array_reverse($InterfaceDirs);
1418 # start out with an empty list 1419 self::$UserInterfacePathsCache[$FilterExp] = array();
1420 $InterfacesFound = array();
1422 # for each possible UI directory 1423 foreach ($InterfaceDirs as $InterfaceDir)
1425 # check if the dir exists 1426 if (!is_dir($InterfaceDir))
1431 $Dir = dir($InterfaceDir);
1433 # for each file in current directory 1434 while (($DirEntry = $Dir->read()) !== FALSE)
1436 $InterfacePath = $InterfaceDir.
"/".$DirEntry;
1438 # skip anything we have already found 1439 # or that doesn't have a name in the required format 1440 # or that isn't a directory 1441 # or that doesn't match the filter regex (if supplied) 1442 if (in_array($DirEntry, $InterfacesFound)
1443 || !preg_match(
'/^[a-zA-Z0-9]+$/', $DirEntry)
1444 || !is_dir($InterfacePath)
1445 || (($FilterExp !== NULL)
1446 && !preg_match($FilterExp, $InterfacePath)))
1451 # add interface to list 1452 self::$UserInterfacePathsCache[$FilterExp][$DirEntry] =
1454 $InterfacesFound[] = $DirEntry;
1461 # return list to caller 1462 return self::$UserInterfacePathsCache[$FilterExp];
1490 &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
1491 &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
1492 &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
1494 $FuncIndex = count($this->PostProcessingFuncs);
1495 $this->PostProcessingFuncs[$FuncIndex][
"FunctionName"] = $FunctionName;
1496 $this->PostProcessingFuncs[$FuncIndex][
"Arguments"] = array();
1498 while (isset(${
"Arg".$Index}) && (${
"Arg".$Index} !== self::NOVALUE))
1500 $this->PostProcessingFuncs[$FuncIndex][
"Arguments"][$Index]
1513 $this->EnvIncludes[] = $FileName;
1534 if (($NewSetting === TRUE)
1535 || ($NewSetting === FALSE)
1536 || is_array($NewSetting))
1538 $this->ContextFilters[$Context] = $NewSetting;
1540 elseif (is_string($NewSetting))
1542 $this->ContextFilters[$Context] = array($NewSetting);
1546 throw new InvalidArgumentException(
1547 "Invalid setting (".$NewSetting.
").");
1571 # determine which location to search based on file suffix 1573 $DirList = ($FileType == self::FT_IMAGE)
1574 ? $this->ImageDirList : $this->IncludeDirList;
1576 # if directed to use minimized JavaScript file 1579 # look for minimized version of file 1580 $MinimizedFileName = substr_replace($FileName,
".min", -3, 0);
1581 $FoundFileName = $this->FindFile($DirList, $MinimizedFileName);
1583 # if minimized file was not found 1584 if (is_null($FoundFileName))
1586 # look for unminimized file 1587 $FoundFileName = $this->FindFile($DirList, $FileName);
1589 # if unminimized file found 1590 if (!is_null($FoundFileName))
1592 # if minimization enabled and supported 1594 && self::JsMinRewriteSupport())
1596 # attempt to create minimized file 1597 $MinFileName = $this->MinimizeJavascriptFile(
1600 # if minimization succeeded 1601 if ($MinFileName !== NULL)
1603 # use minimized version 1604 $FoundFileName = $MinFileName;
1606 # save file modification time if needed for fingerprinting 1609 $FileMTime = filemtime($FoundFileName);
1612 # strip off the cache location, allowing .htaccess 1613 # to handle that for us 1614 $FoundFileName = str_replace(
1615 self::$JSMinCacheDir.
"/",
"", $FoundFileName);
1621 # else if directed to use SCSS files 1624 # look for SCSS version of file 1625 $SourceFileName = preg_replace(
"/.css$/",
".scss", $FileName);
1626 $FoundSourceFileName = $this->FindFile($DirList, $SourceFileName);
1628 # if SCSS file not found 1629 if ($FoundSourceFileName === NULL)
1632 $FoundFileName = $this->FindFile($DirList, $FileName);
1636 # compile SCSS file (if updated) and return resulting CSS file 1637 $FoundFileName = $this->CompileScssFile($FoundSourceFileName);
1639 # save file modification time if needed for fingerprinting 1642 $FileMTime = filemtime($FoundFileName);
1645 # strip off the cache location, allowing .htaccess to handle that for us 1646 if (self::ScssRewriteSupport())
1648 $FoundFileName = str_replace(
1649 self::$ScssCacheDir.
"/",
"", $FoundFileName);
1653 # otherwise just search for the file 1656 $FoundFileName = $this->FindFile($DirList, $FileName);
1659 # add non-image files to list of found files (used for required files loading) 1660 if ($FileType != self::FT_IMAGE)
1661 { $this->FoundUIFiles[] = basename($FoundFileName); }
1663 # if UI file fingerprinting is enabled and supported 1665 && self::UrlFingerprintingRewriteSupport()
1666 && (isset($FileMTime) || file_exists($FoundFileName)))
1668 # if file does not appear to be a server-side inclusion 1669 if (!preg_match(
'/\.(html|php)$/i', $FoundFileName))
1671 # for each URL fingerprinting blacklist entry 1672 $OnBlacklist = FALSE;
1673 foreach ($this->UrlFingerprintBlacklist as $BlacklistEntry)
1675 # if entry looks like a regular expression pattern 1676 if ($BlacklistEntry[0] == substr($BlacklistEntry, -1))
1678 # check file name against regular expression 1679 if (preg_match($BlacklistEntry, $FoundFileName))
1681 $OnBlacklist = TRUE;
1687 # check file name directly against entry 1688 if (basename($FoundFileName) == $BlacklistEntry)
1690 $OnBlacklist = TRUE;
1696 # if file was not on blacklist 1699 # get file modification time if not already retrieved 1700 if (!isset($FileMTime))
1702 $FileMTime = filemtime($FoundFileName);
1705 # add timestamp fingerprint to file name 1706 $Fingerprint = sprintf(
"%06X",
1707 ($FileMTime % 0xFFFFFF));
1708 $FoundFileName = preg_replace(
"/^(.+)\.([a-z]+)$/",
1709 "$1.".$Fingerprint.
".$2",
1715 # return file name to caller 1716 return $FoundFileName;
1729 $FullFileName = $this->
GUIFile($FileName);
1730 if ($FullFileName) { print($FullFileName); }
1749 # convert file name to array if necessary 1750 if (!is_array($FileNames)) { $FileNames = [ $FileNames ]; }
1752 # pad additional attributes if supplied 1753 $AddAttribs = $AdditionalAttributes ?
" ".$AdditionalAttributes :
"";
1756 foreach ($FileNames as $BaseFileName)
1758 # retrieve full file name 1759 $FileName = $this->
GUIFile($BaseFileName);
1764 # print appropriate tag 1765 print $this->GetUIFileLoadingTag($FileName, $AdditionalAttributes);
1768 # if we are not already loading an override file 1769 if (!preg_match(
"/-Override.(css|scss|js)$/", $BaseFileName))
1771 # attempt to load override file if available 1776 $OverrideFileName = preg_replace(
1777 "/\.(css|scss)$/",
"-Override.$1",
1780 $AdditionalAttributes);
1783 case self::FT_JAVASCRIPT:
1784 $OverrideFileName = preg_replace(
1785 "/\.js$/",
"-Override.js",
1788 $AdditionalAttributes);
1803 $this->UrlFingerprintBlacklist[] = $Pattern;
1819 # convert file names to array if necessary 1820 if (!is_array($FileNames)) { $FileNames = [ $FileNames ]; }
1822 # add file names to list of required files 1823 foreach ($FileNames as $FileName)
1825 $this->AdditionalRequiredUIFiles[$FileName] = $Order;
1836 static $FileTypeCache;
1837 if (isset($FileTypeCache[$FileName]))
1839 return $FileTypeCache[$FileName];
1842 $FileSuffix = strtolower(substr($FileName, -3));
1843 if ($FileSuffix ==
"css")
1845 $FileTypeCache[$FileName] = self::FT_CSS;
1847 elseif ($FileSuffix ==
".js")
1849 $FileTypeCache[$FileName] = self::FT_JAVASCRIPT;
1851 elseif (($FileSuffix ==
"gif")
1852 || ($FileSuffix ==
"jpg")
1853 || ($FileSuffix ==
"png")
1854 || ($FileSuffix ==
"svg")
1855 || ($FileSuffix ==
"ico"))
1857 $FileTypeCache[$FileName] = self::FT_IMAGE;
1861 $FileTypeCache[$FileName] = self::FT_OTHER;
1864 return $FileTypeCache[$FileName];
1885 # if specified function is not currently available 1886 if (!is_callable($Callback))
1888 # if function info looks legal 1889 if (is_string($Callback) && strlen($Callback))
1891 # start with function directory list 1892 $Locations = $this->FunctionDirList;
1894 # add object directories to list 1895 $Locations = array_merge(
1896 $Locations, array_keys(self::$ObjectDirectories));
1898 # look for function file 1899 $FunctionFileName = $this->FindFile($Locations,
"F-".$Callback,
1900 array(
"php",
"html"));
1902 # if function file was found 1903 if ($FunctionFileName)
1905 # load function file 1906 include_once($FunctionFileName);
1910 # log error indicating function load failed 1911 $this->
LogError(self::LOGLVL_ERROR,
"Unable to load function" 1912 .
" for callback \"".$Callback.
"\".");
1917 # log error indicating specified function info was bad 1918 $this->
LogError(self::LOGLVL_ERROR,
"Unloadable callback value" 1920 .
" passed to AF::LoadFunction() by " 1921 .StdLib::GetMyCaller().
".");
1925 # report to caller whether function load succeeded 1926 return is_callable($Callback);
1935 return microtime(TRUE) - $this->ExecutionStartTime;
1953 # add new meta tag to list 1954 $this->MetaTags[] = $Attribs;
1967 # add new meta tag to list 1968 $this->UniqueMetaTags[] = [
1969 "Attribs" => $Attribs,
1970 "UniqueAttribs" => $UniqueAttribs,
1977 # ---- Page Caching ------------------------------------------------------ 1993 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2008 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2017 $this->CacheCurrentPage = FALSE;
2029 $Tag = strtolower($Tag);
2031 # if pages were supplied 2032 if ($Pages !== NULL)
2034 # add pages to list for this tag 2035 if (isset($this->PageCacheTags[$Tag]))
2037 $this->PageCacheTags[$Tag] = array_merge(
2038 $this->PageCacheTags[$Tag], $Pages);
2042 $this->PageCacheTags[$Tag] = $Pages;
2047 # add current page to list for this tag 2048 $this->PageCacheTags[$Tag][] =
"CURRENT";
2060 $TagId = $this->GetPageCacheTagId($Tag);
2062 # delete pages and tag/page connections for specified tag 2063 $this->DB->Query(
"DELETE CP, CPTI" 2064 .
" FROM AF_CachedPages CP, AF_CachedPageTagInts CPTI" 2065 .
" WHERE CPTI.TagId = ".intval($TagId)
2066 .
" AND CP.CacheId = CPTI.CacheId");
2074 # clear all page cache tables 2075 $this->DB->Query(
"TRUNCATE TABLE AF_CachedPages");
2076 $this->DB->Query(
"TRUNCATE TABLE AF_CachedPageTags");
2077 $this->DB->Query(
"TRUNCATE TABLE AF_CachedPageTagInts");
2088 $Length = $this->DB->Query(
"SELECT COUNT(*) AS CacheLen" 2089 .
" FROM AF_CachedPages",
"CacheLen");
2090 $Oldest = $this->DB->Query(
"SELECT CachedAt FROM AF_CachedPages" 2091 .
" ORDER BY CachedAt ASC LIMIT 1",
"CachedAt");
2093 "NumberOfEntries" => $Length,
2094 "OldestTimestamp" => strtotime($Oldest),
2106 $this->DB->Query(
"SELECT SUBSTRING_INDEX(Fingerprint, '-', 1) AS PageName," 2107 .
" COUNT(*) AS Number FROM AF_CachedPages" 2108 .
" GROUP BY PageName ORDER BY Number DESC");
2109 return $this->DB->FetchColumn(
"Number",
"PageName");
2115 # ---- Logging ----------------------------------------------------------- 2135 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2151 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2170 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2187 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2205 # if error level is at or below current logging level 2206 if ($this->Settings[
"LoggingLevel"] >= $Level)
2208 # attempt to log error message 2211 # if logging attempt failed and level indicated significant error 2212 if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
2214 # throw exception about inability to log error 2215 static $AlreadyThrewException = FALSE;
2216 if (!$AlreadyThrewException)
2218 $AlreadyThrewException = TRUE;
2219 throw new Exception(
"Unable to log error (".$Level.
": ".$Msg
2220 .
") to ".$this->LogFileName);
2224 # report to caller whether message was logged 2229 # report to caller that message was not logged 2247 # if message level is at or below current logging level 2248 if ($this->Settings[
"LoggingLevel"] >= $Level)
2250 # attempt to open log file 2251 $FHndl = @fopen($this->LogFileName,
"a");
2253 # if log file could not be open 2254 if ($FHndl === FALSE)
2256 # report to caller that message was not logged 2262 $ErrorAbbrevs = array(
2263 self::LOGLVL_FATAL =>
"FTL",
2264 self::LOGLVL_ERROR =>
"ERR",
2265 self::LOGLVL_WARNING =>
"WRN",
2266 self::LOGLVL_INFO =>
"INF",
2267 self::LOGLVL_DEBUG =>
"DBG",
2268 self::LOGLVL_TRACE =>
"TRC",
2270 $Msg = str_replace(array(
"\n",
"\t",
"\r"),
" ", $Msg);
2271 $Msg = substr(trim($Msg), 0, self::LOGFILE_MAX_LINE_LENGTH);
2272 $LogEntry = date(
"Y-m-d H:i:s")
2274 .
" ".$ErrorAbbrevs[$Level]
2277 # write entry to log 2278 $Success = fwrite($FHndl, $LogEntry.
"\n");
2283 # report to caller whether message was logged 2284 return ($Success === FALSE) ? FALSE : TRUE;
2289 # report to caller that message was not logged 2320 # constrain new level (if supplied) to within legal bounds 2323 $NewValue = max(min($NewValue, 6), 1);
2326 # set new logging level (if supplied) and return current level to caller 2327 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2338 if ($NewValue !== NULL) { $this->LogFileName = $NewValue; }
2339 return $this->LogFileName;
2353 # return no entries if there isn't a log file 2354 # or we can't read it or it's empty 2356 if (!is_readable($LogFile) || !filesize($LogFile))
2361 # if max number of entries specified 2364 # load lines from file 2365 $FHandle = fopen($LogFile,
"r");
2366 $FileSize = filesize($LogFile);
2367 $SeekPosition = max(0,
2368 ($FileSize - (self::LOGFILE_MAX_LINE_LENGTH
2370 fseek($FHandle, $SeekPosition);
2371 $Block = fread($FHandle, ($FileSize - $SeekPosition));
2373 $Lines = explode(PHP_EOL, $Block);
2376 # prune array back to requested number of entries 2377 $Lines = array_slice($Lines, (0 - $Limit));
2381 # load all lines from log file 2382 $Lines = file($LogFile, FILE_IGNORE_NEW_LINES);
2383 if ($Lines === FALSE)
2389 # reverse line order 2390 $Lines = array_reverse($Lines);
2392 # for each log file line 2394 foreach ($Lines as $Line)
2396 # attempt to parse line into component parts 2397 $Pieces = explode(
" ", $Line, 5);
2398 $Date = isset($Pieces[0]) ? $Pieces[0] :
"";
2399 $Time = isset($Pieces[1]) ? $Pieces[1] :
"";
2400 $Back = isset($Pieces[2]) ? $Pieces[2] :
"";
2401 $Level = isset($Pieces[3]) ? $Pieces[3] :
"";
2402 $Msg = isset($Pieces[4]) ? $Pieces[4] :
"";
2404 # skip line if it looks invalid 2405 $ErrorAbbrevs = array(
2406 "FTL" => self::LOGLVL_FATAL,
2407 "ERR" => self::LOGLVL_ERROR,
2408 "WRN" => self::LOGLVL_WARNING,
2409 "INF" => self::LOGLVL_INFO,
2410 "DBG" => self::LOGLVL_DEBUG,
2411 "TRC" => self::LOGLVL_TRACE,
2413 if ((($Back !=
"F") && ($Back !=
"B"))
2414 || !array_key_exists($Level, $ErrorAbbrevs)
2420 # convert parts into appropriate values and add to entries 2422 "Time" => strtotime($Date.
" ".$Time),
2423 "Background" => ($Back ==
"B") ? TRUE : FALSE,
2424 "Level" => $ErrorAbbrevs[$Level],
2429 # return entries to caller 2474 # ---- Event Handling ---------------------------------------------------- 2520 # convert parameters to array if not already in that form 2521 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2522 : array($EventsOrEventName => $EventType);
2525 foreach ($Events as $Name => $Type)
2527 # store event information 2528 $this->RegisteredEvents[$Name][
"Type"] = $Type;
2529 $this->RegisteredEvents[$Name][
"Hooks"] = array();
2541 return array_key_exists($EventName, $this->RegisteredEvents)
2553 # the event isn't hooked to if it isn't even registered 2559 # return TRUE if there is at least one callback hooked to the event 2560 return count($this->RegisteredEvents[$EventName][
"Hooks"]) > 0;
2577 $EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
2579 # convert parameters to array if not already in that form 2580 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2581 : array($EventsOrEventName => $Callback);
2585 foreach ($Events as $EventName => $EventCallback)
2587 # if callback is valid 2588 if (is_callable($EventCallback))
2590 # if this is a periodic event we process internally 2591 if (isset($this->PeriodicEvents[$EventName]))
2594 $this->ProcessPeriodicEvent($EventName, $EventCallback);
2596 # if specified event has been registered 2597 elseif (isset($this->RegisteredEvents[$EventName]))
2599 # add callback for event 2600 $this->RegisteredEvents[$EventName][
"Hooks"][]
2601 = array(
"Callback" => $EventCallback,
"Order" => $Order);
2603 # sort callbacks by order 2604 if (count($this->RegisteredEvents[$EventName][
"Hooks"]) > 1)
2606 usort($this->RegisteredEvents[$EventName][
"Hooks"],
2609 $A[
"Order"], $B[
"Order"]);
2624 # report to caller whether all callbacks were hooked 2642 $EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
2644 # convert parameters to array if not already in that form 2645 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2646 : array($EventsOrEventName => $Callback);
2650 foreach ($Events as $EventName => $EventCallback)
2652 # if this event has been registered and hooked 2653 if (isset($this->RegisteredEvents[$EventName])
2654 && count($this->RegisteredEvents[$EventName]))
2656 # if this callback has been hooked for this event 2657 $CallbackData = array(
"Callback" => $EventCallback,
"Order" => $Order);
2658 if (in_array($CallbackData,
2659 $this->RegisteredEvents[$EventName][
"Hooks"]))
2662 $HookIndex = array_search($CallbackData,
2663 $this->RegisteredEvents[$EventName][
"Hooks"]);
2664 unset($this->RegisteredEvents[$EventName][
"Hooks"][$HookIndex]);
2670 # report number of callbacks unhooked to caller 2671 return $UnhookCount;
2686 $ReturnValue = NULL;
2688 # if event has been registered 2689 if (isset($this->RegisteredEvents[$EventName]))
2691 # set up default return value (if not NULL) 2692 switch ($this->RegisteredEvents[$EventName][
"Type"])
2694 case self::EVENTTYPE_CHAIN:
2695 $ReturnValue = $Parameters;
2698 case self::EVENTTYPE_NAMED:
2699 $ReturnValue = array();
2703 # for each callback for this event 2704 foreach ($this->RegisteredEvents[$EventName][
"Hooks"] as $Hook)
2707 $Callback = $Hook[
"Callback"];
2708 $Result = ($Parameters !== NULL)
2709 ? call_user_func_array($Callback, $Parameters)
2710 : call_user_func($Callback);
2712 # process return value based on event type 2713 switch ($this->RegisteredEvents[$EventName][
"Type"])
2715 case self::EVENTTYPE_CHAIN:
2716 if ($Result !== NULL)
2718 foreach ($Parameters as $Index => $Value)
2720 if (array_key_exists($Index, $Result))
2722 $Parameters[$Index] = $Result[$Index];
2725 $ReturnValue = $Parameters;
2729 case self::EVENTTYPE_FIRST:
2730 if ($Result !== NULL)
2732 $ReturnValue = $Result;
2737 case self::EVENTTYPE_NAMED:
2738 $CallbackName = is_array($Callback)
2739 ? (is_object($Callback[0])
2740 ? get_class($Callback[0])
2741 : $Callback[0]).
"::".$Callback[1]
2743 $ReturnValue[$CallbackName] = $Result;
2753 $this->
LogError(self::LOGLVL_WARNING,
2754 "Unregistered event (".$EventName.
") signaled by " 2755 .StdLib::GetMyCaller().
".");
2758 # return value if any to caller 2759 return $ReturnValue;
2769 return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
2784 # if event is not a periodic event report failure to caller 2785 if (!array_key_exists($EventName, $this->EventPeriods)) {
return FALSE; }
2787 # retrieve last execution time for event if available 2788 $Signature = self::GetCallbackSignature($Callback);
2789 $LastRunTime = $this->DB->Query(
"SELECT LastRunAt FROM PeriodicEvents" 2790 .
" WHERE Signature = '".addslashes($Signature).
"'",
"LastRunAt");
2792 # if event was not found report failure to caller 2793 if ($LastRunTime === NULL) {
return FALSE; }
2795 # calculate next run time based on event period 2796 $NextRunTime = strtotime($LastRunTime) + $this->EventPeriods[$EventName];
2798 # report next run time to caller 2799 return $NextRunTime;
2819 # retrieve last execution times 2820 $this->DB->Query(
"SELECT * FROM PeriodicEvents");
2821 $LastRunTimes = $this->DB->FetchColumn(
"LastRunAt",
"Signature");
2823 # for each known event 2825 foreach ($this->KnownPeriodicEvents as $Signature => $Info)
2827 # if last run time for event is available 2828 if (array_key_exists($Signature, $LastRunTimes))
2830 # calculate next run time for event 2831 $LastRun = strtotime($LastRunTimes[$Signature]);
2832 $NextRun = $LastRun + $this->EventPeriods[$Info[
"Period"]];
2833 if ($Info[
"Period"] ==
"EVENT_PERIODIC") { $LastRun = FALSE; }
2837 # set info to indicate run times are not known 2842 # add event info to list 2843 $Events[$Signature] = $Info;
2844 $Events[$Signature][
"LastRun"] = $LastRun;
2845 $Events[$Signature][
"NextRun"] = $NextRun;
2846 $Events[$Signature][
"Parameters"] = NULL;
2849 # return list of known events to caller 2860 $EventName, $Callback, $Parameters)
2863 if (!isset($DB)) { $DB =
new Database(); }
2866 $ReturnVal = call_user_func_array($Callback, $Parameters);
2868 # if event is already in database 2869 $Signature = self::GetCallbackSignature($Callback);
2870 if ($DB->Query(
"SELECT COUNT(*) AS EventCount FROM PeriodicEvents" 2871 .
" WHERE Signature = '".addslashes($Signature).
"'",
"EventCount"))
2873 # update last run time for event 2874 $DB->Query(
"UPDATE PeriodicEvents SET LastRunAt = " 2875 .(($EventName ==
"EVENT_PERIODIC")
2876 ?
"'".date(
"Y-m-d H:i:s", time() + ($ReturnVal * 60)).
"'" 2878 .
" WHERE Signature = '".addslashes($Signature).
"'");
2882 # add last run time for event to database 2883 $DB->Query(
"INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES " 2884 .
"('".addslashes($Signature).
"', " 2885 .(($EventName ==
"EVENT_PERIODIC")
2886 ?
"'".date(
"Y-m-d H:i:s", time() + ($ReturnVal * 60)).
"'" 2894 # ---- Task Management --------------------------------------------------- 2913 return $this->TaskMgr;
2930 $Priority = self::PRIORITY_LOW, $Description =
"")
2932 call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
2954 $Priority = self::PRIORITY_LOW, $Description =
"")
2956 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
2970 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
2980 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
2992 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3009 $Parameters = NULL, $Priority = NULL, $Description = NULL)
3011 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3023 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3035 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3044 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3054 call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3065 call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3075 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3087 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3103 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3116 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3128 return call_user_func_array([
"AFTaskManager", __FUNCTION__], func_get_args());
3138 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3152 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3166 return call_user_func_array([$this->
TaskMgr, __FUNCTION__], func_get_args());
3172 # ---- Clean URL Support ------------------------------------------------- 3203 $GetVars = NULL, $Template = NULL)
3205 # save clean URL mapping parameters 3206 $this->CleanUrlMappings[] = array(
3207 "Pattern" => $Pattern,
3209 "GetVars" => $GetVars,
3213 # if replacement template specified 3214 if ($Template !== NULL)
3216 # if GET parameters specified 3217 if (count($GetVars))
3219 # retrieve all possible permutations of GET parameters 3222 # for each permutation of GET parameters 3223 foreach ($GetPerms as $VarPermutation)
3225 # construct search pattern for permutation 3226 $SearchPattern =
"/href=([\"'])index\\.php\\?P=".$Page;
3227 $GetVarSegment =
"";
3228 foreach ($VarPermutation as $GetVar)
3230 if (preg_match(
"%\\\$[0-9]+%", $GetVars[$GetVar]))
3232 $GetVarSegment .=
"&".$GetVar.
"=((?:(?!\\1)[^&])+)";
3236 $GetVarSegment .=
"&".$GetVar.
"=".$GetVars[$GetVar];
3239 $SearchPattern .= $GetVarSegment.
"\\1/i";
3241 # if template is actually a callback 3242 if (is_callable($Template))
3244 # add pattern to HTML output mod callbacks list 3245 $this->OutputModificationCallbacks[] = array(
3246 "Pattern" => $Pattern,
3248 "SearchPattern" => $SearchPattern,
3249 "Callback" => $Template,
3254 # construct replacement string for permutation 3255 $Replacement = $Template;
3257 foreach ($VarPermutation as $GetVar)
3259 $Replacement = str_replace(
3260 "\$".$GetVar,
"\$".$Index, $Replacement);
3263 $Replacement =
"href=\"".$Replacement.
"\"";
3265 # add pattern to HTML output modifications list 3266 $this->OutputModificationPatterns[] = $SearchPattern;
3267 $this->OutputModificationReplacements[] = $Replacement;
3273 # construct search pattern 3274 $SearchPattern =
"/href=\"index\\.php\\?P=".$Page.
"\"/i";
3276 # if template is actually a callback 3277 if (is_callable($Template))
3279 # add pattern to HTML output mod callbacks list 3280 $this->OutputModificationCallbacks[] = array(
3281 "Pattern" => $Pattern,
3283 "SearchPattern" => $SearchPattern,
3284 "Callback" => $Template,
3289 # add simple pattern to HTML output modifications list 3290 $this->OutputModificationPatterns[] = $SearchPattern;
3291 $this->OutputModificationReplacements[] =
"href=\"".$Template.
"\"";
3304 foreach ($this->CleanUrlMappings as $Info)
3306 if (preg_match($Info[
"Pattern"], $Path))
3325 # the search patterns and callbacks require a specific format 3326 $Format =
"href=\"".str_replace(
"&",
"&", $Path).
"\"";
3329 # perform any regular expression replacements on the search string 3330 $Search = preg_replace($this->OutputModificationPatterns,
3331 $this->OutputModificationReplacements, $Search);
3333 # only run the callbacks if a replacement hasn't already been performed 3334 if ($Search == $Format)
3336 # perform any callback replacements on the search string 3337 foreach ($this->OutputModificationCallbacks as $Info)
3339 # make the information available to the callback 3340 $this->OutputModificationCallbackInfo = $Info;
3342 # execute the callback 3343 $Search = preg_replace_callback($Info[
"SearchPattern"],
3344 array($this,
"OutputModificationCallbackShell"),
3349 # return the path untouched if no replacements were performed 3350 if ($Search == $Format)
3355 # remove the bits added to the search string to get it recognized by 3356 # the replacement expressions and callbacks 3357 $Result = substr($Search, 6, -1);
3370 # for each clean URL mapping 3371 foreach ($this->CleanUrlMappings as $Info)
3373 # if current path matches the clean URL pattern 3374 if (preg_match($Info[
"Pattern"], $Path, $Matches))
3376 # the GET parameters for the URL, starting with the page name 3377 $GetVars = array(
"P" => $Info[
"Page"]);
3379 # if additional $_GET variables specified for clean URL 3380 if ($Info[
"GetVars"] !== NULL)
3382 # for each $_GET variable specified for clean URL 3383 foreach ($Info[
"GetVars"] as $VarName => $VarTemplate)
3385 # start with template for variable value 3386 $Value = $VarTemplate;
3388 # for each subpattern matched in current URL 3389 foreach ($Matches as $Index => $Match)
3391 # if not first (whole) match 3394 # make any substitutions in template 3395 $Value = str_replace(
"$".$Index, $Match, $Value);
3399 # add the GET variable 3400 $GetVars[$VarName] = $Value;
3404 # return the unclean URL 3405 return "index.php?" . http_build_query($GetVars);
3409 # return the path unchanged 3429 $GetVars = array(
"P" => $this->
GetPageName()) + $_GET;
3430 return "index.php?" . http_build_query($GetVars);
3442 return $this->CleanUrlMappings;
3459 $this->AlternateDomainPrefixes[$Domain] = $Prefix;
3469 return array_keys($this->AlternateDomainPrefixes);
3480 return isset($this->AlternateDomainPrefixes[$Domain]) ?
3481 $this->AlternateDomainPrefixes[$Domain] : NULL;
3486 # ---- Server Environment ------------------------------------------------ 3497 if ($NewValue !== NULL)
3499 self::$SessionLifetime = $NewValue;
3501 return self::$SessionLifetime;
3511 return isset($_SERVER[
"HTACCESS_SUPPORT"])
3512 || isset($_SERVER[
"REDIRECT_HTACCESS_SUPPORT"]);
3523 return isset($_SERVER[
"URL_FINGERPRINTING_SUPPORT"])
3524 || isset($_SERVER[
"REDIRECT_URL_FINGERPRINTING_SUPPORT"]);
3535 return isset($_SERVER[
"SCSS_REWRITE_SUPPORT"])
3536 || isset($_SERVER[
"REDIRECT_SCSS_REWRITE_SUPPORT"]);
3547 return isset($_SERVER[
"JSMIN_REWRITE_SUPPORT"])
3548 || isset($_SERVER[
"REDIRECT_JSMIN_REWRITE_SUPPORT"]);
3560 # return override value if one is set 3561 if (self::$RootUrlOverride !== NULL)
3563 return self::$RootUrlOverride;
3566 # determine scheme name 3567 $Protocol = (isset($_SERVER[
"HTTPS"]) ?
"https" :
"http");
3569 # if HTTP_HOST is preferred or SERVER_NAME points to localhost 3570 # and HTTP_HOST is set 3571 if ((self::$PreferHttpHost || ($_SERVER[
"SERVER_NAME"] ==
"127.0.0.1"))
3572 && isset($_SERVER[
"HTTP_HOST"]))
3574 # use HTTP_HOST for domain name 3575 $DomainName = $_SERVER[
"HTTP_HOST"];
3579 # use SERVER_NAME for domain name 3580 $DomainName = $_SERVER[
"SERVER_NAME"];
3583 # build URL root and return to caller 3584 return $Protocol.
"://".$DomainName;
3603 if ($NewValue !== self::NOVALUE)
3605 self::$RootUrlOverride = strlen(trim($NewValue)) ? $NewValue : NULL;
3607 return self::$RootUrlOverride;
3621 $BaseUrl = self::RootUrl().dirname($_SERVER[
"SCRIPT_NAME"]);
3622 if (substr($BaseUrl, -1) !=
"/") { $BaseUrl .=
"/"; }
3635 return self::RootUrl().$_SERVER[
"REQUEST_URI"];
3650 if ($NewValue !== NULL)
3652 self::$PreferHttpHost = ($NewValue ? TRUE : FALSE);
3654 return self::$PreferHttpHost;
3663 $BasePath = dirname($_SERVER[
"SCRIPT_NAME"]);
3665 if (substr($BasePath, -1) !=
"/")
3680 if (array_key_exists(
"SCRIPT_URL", $_SERVER))
3682 return $_SERVER[
"SCRIPT_URL"];
3684 elseif (array_key_exists(
"REQUEST_URI", $_SERVER))
3686 $Pieces = parse_url($_SERVER[
"REQUEST_URI"]);
3687 return isset($Pieces[
"path"]) ? $Pieces[
"path"] : NULL;
3689 elseif (array_key_exists(
"REDIRECT_URL", $_SERVER))
3691 return $_SERVER[
"REDIRECT_URL"];
3709 # needed to get the path of the URL minus the query and fragment pieces 3710 $Components = parse_url(self::GetScriptUrl());
3712 # if parsing was successful and a path is set 3713 if (is_array($Components) && isset($Components[
"path"]))
3715 $BasePath = self::BasePath();
3716 $Path = $Components[
"path"];
3718 # the URL was rewritten if the path isn't the base path, i.e., the 3719 # home page, and the file in the URL isn't the script generating the 3721 if ($BasePath != $Path && basename($Path) != $ScriptName)
3727 # the URL wasn't rewritten 3742 if ($NewSetting !== NULL)
3744 self::$IsAjaxPageLoad = $NewSetting;
3747 if (isset(self::$IsAjaxPageLoad))
3749 return self::$IsAjaxPageLoad;
3751 elseif (isset($_SERVER[
"HTTP_X_REQUESTED_WITH"])
3752 && (strtolower($_SERVER[
"HTTP_X_REQUESTED_WITH"])
3753 ==
"xmlhttprequest"))
3770 return self::GetPhpMemoryLimit() - memory_get_usage(TRUE);
3780 return (self::GetFreeMemory() / self::GetPhpMemoryLimit()) * 100;
3790 return self::ConvertPhpIniSizeToBytes(
3791 ini_get(
"memory_limit"));
3803 self::ConvertPhpIniSizeToBytes(
3804 ini_get(
"post_max_size")),
3805 self::ConvertPhpIniSizeToBytes(
3806 ini_get(
"upload_max_filesize")));
3823 if ($NewValue !== NULL)
3825 $NewValue = max($NewValue, 5);
3826 ini_set(
"max_execution_time", $NewValue);
3828 $this->UpdateSetting(
"MaxExecTime", $NewValue, $Persistent);
3830 return ini_get(
"max_execution_time");
3849 return $this->UpdateSetting(
"DbSlowQueryThresholdForeground", $NewValue);
3868 return $this->UpdateSetting(
"DbSlowQueryThresholdBackground", $NewValue);
3877 # ---- Utility ----------------------------------------------------------- 3895 # check that file is readable 3896 if (!is_readable($FilePath))
3901 # if file name was not supplied 3902 if ($FileName === NULL)
3904 # extract file name from path 3905 $FileName = basename($FilePath);
3908 # if MIME type was not supplied 3909 if ($MimeType === NULL)
3911 # attempt to determine MIME type 3912 $FInfoHandle = finfo_open(FILEINFO_MIME);
3915 $FInfoMime = finfo_file($FInfoHandle, $FilePath);
3916 finfo_close($FInfoHandle);
3919 $MimeType = $FInfoMime;
3923 # use default if unable to determine MIME type 3924 if ($MimeType === NULL)
3926 $MimeType =
"application/octet-stream";
3930 # list of mime types where we allow the browser to decide on 3931 # how to display the item by omitting the Content-Disposition 3940 # set headers to download file 3941 header(
"Content-Type: ".$MimeType);
3942 header(
"Content-Length: ".filesize($FilePath));
3943 if ($this->CleanUrlRewritePerformed &&
3944 !in_array($MimeType, $InlineTypes))
3946 header(
'Content-Disposition: attachment; filename="'.$FileName.
'"');
3949 # make sure that apache does not attempt to compress file 3950 apache_setenv(
'no-gzip',
'1');
3952 # send file to user, but unbuffered to avoid memory issues 3955 $BlockSize = 512000;
3957 $Handle = @fopen($File,
"rb");
3958 if ($Handle === FALSE)
3963 # (close out session, making it read-only, so that session file 3964 # lock is released and others are not potentially hanging 3965 # waiting for it while the download completes) 3966 session_write_close();
3968 while (!feof($Handle))
3970 print fread($Handle, $BlockSize);
3975 }, array($FilePath));
3977 # prevent HTML output that might interfere with download 3980 # set flag to indicate not to log a slow page load in case client 3981 # connection delays PHP execution because of header 3982 $this->DoNotLogSlowPageLoad = TRUE;
3984 # report no errors found to caller 4002 # assume we will not get a lock 4005 # clear out any stale locks 4006 static $CleanupHasBeenDone = FALSE;
4007 if (!$CleanupHasBeenDone)
4009 # (margin for clearing stale locks is twice the known 4010 # maximum PHP execution time, because the max time 4011 # techinically does not include external operations 4012 # like database queries) 4015 $this->DB->Query(
"DELETE FROM AF_Locks WHERE" 4016 .
" ObtainedAt < '".$ClearLocksObtainedBefore.
"' AND" 4017 .
" LockName = '".addslashes($LockName).
"'");
4022 # lock database table so nobody else can try to get a lock 4023 $this->DB->Query(
"LOCK TABLES AF_Locks WRITE");
4025 # look for lock with specified name 4026 $FoundCount = $this->DB->Query(
"SELECT COUNT(*) AS FoundCount" 4027 .
" FROM AF_Locks WHERE LockName = '" 4028 .addslashes($LockName).
"'",
"FoundCount");
4029 $LockFound = ($FoundCount > 0) ? TRUE : FALSE;
4034 # unlock database tables 4035 $this->DB->Query(
"UNLOCK TABLES");
4037 # if blocking was requested 4040 # wait to give someone else a chance to release lock 4046 # while lock was found and blocking was requested 4047 }
while ($LockFound && $Wait);
4050 # if lock was not found 4054 $this->DB->Query(
"INSERT INTO AF_Locks (LockName) VALUES ('" 4055 .addslashes($LockName).
"')");
4058 # unlock database tables 4059 $this->DB->Query(
"UNLOCK TABLES");
4062 # report to caller whether lock was obtained 4075 # release any existing locks 4076 $this->DB->Query(
"DELETE FROM AF_Locks WHERE LockName = '" 4077 .addslashes($LockName).
"'");
4079 # report to caller whether existing lock was released 4080 return $this->DB->NumRowsAffected() ? TRUE : FALSE;
4102 $ResponseType =
"JSON",
4103 $CloseSession = TRUE)
4105 switch ($ResponseType)
4109 header(
"Content-Type: application/json; charset=" 4110 .$GLOBALS[
"G_SysConfig"]->DefaultCharacterSet(), TRUE);
4114 header(
"Content-Type: application/xml; charset=" 4115 .$GLOBALS[
"G_SysConfig"]->DefaultCharacterSet(), TRUE);
4120 throw new Exception(
4121 "Unsupported response type: ".$ResponseType);
4125 self::$DefaultBrowserCacheExpiration);
4129 session_write_close();
4141 # set headers to control caching 4142 header(
"Expires: ".gmdate(
"D, d M Y H:i:s \G\M\T", time()+$MaxAge));
4143 header(
"Cache-Control: private, max-age=".$MaxAge);
4155 $Str = strtoupper($Size);
4157 # trim off 'B' suffix for KB/MB/GB 4158 if (substr($Str, -1) ==
"B")
4160 $Str = substr($Str, 0, strlen($Str) - 1);
4163 # pull out the numeric part of our setting 4164 $Val = intval($Str);
4166 # adjust it based on the units 4167 switch (substr($Str, -1))
4196 if ($InUse !== NULL)
4201 return $this->SessionInUse;
4207 # ---- Backward Compatibility -------------------------------------------- 4219 return $this->FindFile(
4220 $this->IncludeDirList, $BaseName, array(
"tpl",
"html"));
4226 # ---- PRIVATE INTERFACE ------------------------------------------------- 4228 private $AdditionalRequiredUIFiles = array();
4229 private $AlternateDomainPrefixes = array();
4230 private $BrowserDetectFunc;
4231 private $CacheCurrentPage = TRUE;
4232 private $CleanUrlMappings = array();
4233 private $CleanUrlRewritePerformed = FALSE;
4234 private $ContextFilters = array(
4235 self::CONTEXT_START => TRUE,
4236 self::CONTEXT_PAGE => array(
"H_"),
4237 self::CONTEXT_COMMON => array(
"H_"),
4239 private $CssUrlFingerprintPath;
4241 private $DefaultPage =
"Home";
4242 private $DoNotMinimizeList = array();
4243 private $DoNotLogSlowPageLoad = FALSE;
4244 private $EnvIncludes = array();
4245 private $ExecutionStartTime;
4246 private $FoundUIFiles = array();
4247 private $HtmlCharset =
"UTF-8";
4248 private $InterfaceSettings = array();
4249 private $JSMinimizerJavaScriptPackerAvailable = FALSE;
4250 private $JSMinimizerJShrinkAvailable = TRUE;
4251 private $JumpToPage = NULL;
4252 private $JumpToPageDelay = 0;
4253 private $LogFileName =
"local/logs/site.log";
4254 private $MetaTags = array();
4255 private $OutputModificationCallbackInfo;
4256 private $OutputModificationCallbacks = array();
4257 private $OutputModificationPatterns = array();
4258 private $OutputModificationReplacements = array();
4259 private $PageCacheTags = array();
4261 private $PostProcessingFuncs = array();
4262 private $RunningInBackground = FALSE;
4263 private $SavedContext;
4264 private $SaveTemplateLocationCache = FALSE;
4265 private $SessionStorage;
4266 private $SessionGcProbability;
4268 private $SuppressHTML = FALSE;
4269 private $SuppressStdPageStartAndEnd = FALSE;
4271 private $TemplateLocationCache;
4272 private $TemplateLocationCacheInterval = 60; # in minutes
4273 private $TemplateLocationCacheExpiration;
4274 private $UnbufferedCallbacks = array();
4275 private $UniqueMetaTags = array();
4276 private $UrlFingerprintBlacklist = array();
4277 private $UseBaseTag = FALSE;
4278 private $SessionInUse = FALSE;
4280 private static $ActiveUI =
"default";
4281 private static $AppName =
"ScoutAF";
4282 private static $DefaultBrowserCacheExpiration = 30; # in seconds
4283 private static $DefaultUI =
"default";
4284 private static $IsAjaxPageLoad;
4285 private static $JSMinCacheDir =
"local/data/caches/JSMin";
4286 private static $NamespaceDirectories = array();
4287 private static $ObjectDirectories = array();
4288 private static $ObjectLocationCache;
4289 private static $ObjectLocationCacheInterval = 60;
4290 private static $ObjectLocationCacheExpiration;
4291 private static $PreferHttpHost = FALSE;
4292 private static $RootUrlOverride = NULL;
4293 private static $SaveObjectLocationCache = FALSE;
4294 private static $ScssCacheDir =
"local/data/caches/SCSS";
4295 private static $SessionLifetime = 1440; # in seconds
4296 private static $SuppressSessionInitialization = FALSE;
4297 private static $UserInterfaceListCache = array();
4298 private static $UserInterfacePathsCache = array();
4300 # offset used to generate page cache tag IDs from numeric tags 4303 # minimum expired session garbage collection probability 4310 private $NoTSR = FALSE;
4312 private $RegisteredEvents = array();
4313 private $KnownPeriodicEvents = array();
4314 private $PeriodicEvents = array(
4315 "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
4316 "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
4317 "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
4318 "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
4319 "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
4321 private $EventPeriods = array(
4322 "EVENT_HOURLY" => 3600,
4323 "EVENT_DAILY" => 86400,
4324 "EVENT_WEEKLY" => 604800,
4325 "EVENT_MONTHLY" => 2592000,
4326 "EVENT_PERIODIC" => 0,
4328 private $UIEvents = array(
4329 "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
4330 "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
4331 "EVENT_PHP_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
4332 "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
4333 "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
4334 "EVENT_PAGE_OUTPUT_FILTER" => self::EVENTTYPE_CHAIN,
4341 private function LoadSettings()
4343 # read settings in from database 4344 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
4345 $this->Settings = $this->DB->FetchRow();
4347 # if settings were not previously initialized 4348 if ($this->Settings === FALSE)
4350 # initialize settings in database 4351 $this->DB->Query(
"INSERT INTO ApplicationFrameworkSettings" 4352 .
" (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
4354 # read new settings in from database 4355 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
4356 $this->Settings = $this->DB->FetchRow();
4358 # bail out if reloading new settings failed 4359 if ($this->Settings === FALSE)
4361 throw new Exception(
4362 "Unable to load application framework settings.");
4366 # if base path was not previously set or we appear to have moved 4367 if (!array_key_exists(
"BasePath", $this->Settings)
4368 || (!strlen($this->Settings[
"BasePath"]))
4369 || (!array_key_exists(
"BasePathCheck", $this->Settings))
4370 || (__FILE__ != $this->Settings[
"BasePathCheck"]))
4372 # attempt to extract base path from Apache .htaccess file 4373 if (is_readable(
".htaccess"))
4375 $Lines = file(
".htaccess");
4376 foreach ($Lines as $Line)
4378 if (preg_match(
"/\\s*RewriteBase\\s+/", $Line))
4380 $Pieces = preg_split(
4381 "/\\s+/", $Line, NULL, PREG_SPLIT_NO_EMPTY);
4382 $BasePath = $Pieces[1];
4387 # if base path was found 4388 if (isset($BasePath))
4390 # save base path locally 4391 $this->Settings[
"BasePath"] = $BasePath;
4393 # save base path to database 4394 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings" 4395 .
" SET BasePath = '".addslashes($BasePath).
"'" 4396 .
", BasePathCheck = '".addslashes(__FILE__).
"'");
4400 # retrieve template location cache 4401 $this->TemplateLocationCache = unserialize(
4402 $this->Settings[
"TemplateLocationCache"]);
4403 $this->TemplateLocationCacheInterval =
4404 $this->Settings[
"TemplateLocationCacheInterval"];
4405 $this->TemplateLocationCacheExpiration =
4406 strtotime($this->Settings[
"TemplateLocationCacheExpiration"]);
4408 # if template location cache looks invalid or has expired 4409 $CurrentTime = time();
4410 if (!is_array($this->TemplateLocationCache)
4411 || !count($this->TemplateLocationCache)
4412 || ($CurrentTime >= $this->TemplateLocationCacheExpiration))
4414 # clear cache and reset cache expiration 4415 $this->TemplateLocationCache = array();
4416 $this->TemplateLocationCacheExpiration =
4417 $CurrentTime + ($this->TemplateLocationCacheInterval * 60);
4418 $this->SaveTemplateLocationCache = TRUE;
4421 # retrieve object location cache 4422 self::$ObjectLocationCache =
4423 unserialize($this->Settings[
"ObjectLocationCache"]);
4424 self::$ObjectLocationCacheInterval =
4425 $this->Settings[
"ObjectLocationCacheInterval"];
4426 self::$ObjectLocationCacheExpiration =
4427 strtotime($this->Settings[
"ObjectLocationCacheExpiration"]);
4429 # if object location cache looks invalid or has expired 4430 if (!is_array(self::$ObjectLocationCache)
4431 || !count(self::$ObjectLocationCache)
4432 || ($CurrentTime >= self::$ObjectLocationCacheExpiration))
4434 # clear cache and reset cache expiration 4435 self::$ObjectLocationCache = array();
4436 self::$ObjectLocationCacheExpiration =
4437 $CurrentTime + (self::$ObjectLocationCacheInterval * 60);
4438 self::$SaveObjectLocationCache = TRUE;
4448 private function RewriteCleanUrls($PageName)
4450 # if URL rewriting is supported by the server 4453 # retrieve current URL and remove base path if present 4456 # for each clean URL mapping 4457 foreach ($this->CleanUrlMappings as $Info)
4459 # if current URL matches clean URL pattern 4460 if (preg_match($Info[
"Pattern"], $Url, $Matches))
4463 $PageName = $Info[
"Page"];
4465 # if $_GET variables specified for clean URL 4466 if ($Info[
"GetVars"] !== NULL)
4468 # for each $_GET variable specified for clean URL 4469 foreach ($Info[
"GetVars"] as $VarName => $VarTemplate)
4471 # start with template for variable value 4472 $Value = $VarTemplate;
4474 # for each subpattern matched in current URL 4475 foreach ($Matches as $Index => $Match)
4477 # if not first (whole) match 4480 # make any substitutions in template 4481 $Value = str_replace(
"$".$Index, $Match, $Value);
4485 # set $_GET variable 4486 $_GET[$VarName] = $Value;
4490 # set flag indicating clean URL mapped 4491 $this->CleanUrlRewritePerformed = TRUE;
4493 # stop looking for a mapping 4499 # return (possibly) updated page name to caller 4515 private function RewriteAlternateDomainUrls($Html)
4517 # if we were loaded via an alternate domain, and we have a 4518 # RootUrlOverride configured to tell us which domain is the 4519 # primary, and if rewriting support is enabled, then we can 4520 # handle URL Rewriting 4521 if ($this->LoadedViaAlternateDomain() &&
4522 self::$RootUrlOverride !== NULL &&
4525 # pull out the configured prefix for this domain 4526 $VHost = $_SERVER[
"SERVER_NAME"];
4527 $ThisPrefix = $this->AlternateDomainPrefixes[$VHost];
4529 # get the URL for the primary domain, including the base path 4530 # (usually the part between the host name and the PHP file name) 4531 $RootUrl = $this->
RootUrl().self::BasePath();
4533 # and figure out what protcol we were using 4534 $Protocol = (isset($_SERVER[
"HTTPS"]) ?
"https" :
"http");
4536 # NB: preg_replace iterates through the configured 4537 # search/replacement pairs, such that the second one 4538 # runs after the first and so on 4540 # the first n-1 patterns below convert any relative 4541 # links in the generated HTML to absolute links using 4542 # our primary domain (e.g., for stylesheets, javascript, 4545 # the nth pattern looks for links that live within the 4546 # path subtree specified by our configured prefix on 4547 # our primary domain, then replaces them with equivalent 4548 # links on our secondary domain 4550 # for example, if our primary domain is 4551 # example.com/MySite and our secondary domain is 4552 # things.example.org/MySite with 'things' as the 4553 # configured prefix, then this last pattern will look 4554 # for example.com/MySite/things and replace it with 4555 # things.example.org/MySite 4556 $RelativePathPatterns = array(
4557 "%src=\"(?!http://|https://)%i",
4558 "%src='(?!http://|https://)%i",
4559 "%href=\"(?!http://|https://)%i",
4560 "%href='(?!http://|https://)%i",
4561 "%action=\"(?!http://|https://)%i",
4562 "%action='(?!http://|https://)%i",
4563 "%@import\s+url\(\"(?!http://|https://)%i",
4564 "%@import\s+url\('(?!http://|https://)%i",
4565 "%src:\s+url\(\"(?!http://|https://)%i",
4566 "%src:\s+url\('(?!http://|https://)%i",
4567 "%@import\s+\"(?!http://|https://)%i",
4568 "%@import\s+'(?!http://|https://)%i",
4569 "%".preg_quote($RootUrl.$ThisPrefix.
"/",
"%").
"%",
4571 $RelativePathReplacements = array(
4576 "action=\"".$RootUrl,
4577 "action='".$RootUrl,
4578 "@import url(\"".$RootUrl,
4579 "@import url('".$RootUrl,
4580 "src: url(\"".$RootUrl,
4581 "src: url('".$RootUrl,
4582 "@import \"".$RootUrl,
4583 "@import '".$RootUrl,
4584 $Protocol.
"://".$VHost.self::BasePath(),
4587 $NewHtml = preg_replace(
4588 $RelativePathPatterns,
4589 $RelativePathReplacements,
4592 # check to make sure relative path fixes didn't fail 4593 $Html = $this->CheckOutputModification(
4595 "alternate domain substitutions");
4605 private function LoadedViaAlternateDomain()
4607 return (isset($_SERVER[
"SERVER_NAME"]) &&
4608 isset($this->AlternateDomainPrefixes[$_SERVER[
"SERVER_NAME"]])) ?
4630 private function FindFile($DirectoryList, $BaseName,
4631 $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
4633 # generate template cache index for this page 4634 $CacheIndex = md5(serialize($DirectoryList))
4635 .self::$DefaultUI.self::$ActiveUI.$BaseName;
4637 # if caching is enabled and we have cached location 4638 if (($this->TemplateLocationCacheInterval > 0)
4639 && array_key_exists($CacheIndex,
4640 $this->TemplateLocationCache))
4642 # use template location from cache 4643 $FoundFileName = $this->TemplateLocationCache[$CacheIndex];
4647 # if suffixes specified and base name does not include suffix 4648 if ($PossibleSuffixes !== NULL
4649 && count($PossibleSuffixes)
4650 && !preg_match(
"/\.[a-zA-Z0-9]+$/", $BaseName))
4652 # add versions of file names with suffixes to file name list 4653 $FileNames = array();
4654 foreach ($PossibleSuffixes as $Suffix)
4656 $FileNames[] = $BaseName.
".".$Suffix;
4661 # use base name as file name 4662 $FileNames = array($BaseName);
4665 # if prefixes specified 4666 if ($PossiblePrefixes !== NULL && count($PossiblePrefixes))
4668 # add versions of file names with prefixes to file name list 4669 $NewFileNames = array();
4670 foreach ($FileNames as $FileName)
4672 foreach ($PossiblePrefixes as $Prefix)
4674 $NewFileNames[] = $Prefix.$FileName;
4677 $FileNames = $NewFileNames;
4680 # expand directory list to include variants 4681 $DirectoryList = $this->ExpandDirectoryList($DirectoryList);
4683 # for each possible location 4684 $FoundFileName = NULL;
4685 foreach ($DirectoryList as $Dir)
4687 # for each possible file name 4688 foreach ($FileNames as $File)
4690 # if template is found at location 4691 if (file_exists($Dir.$File))
4693 # save full template file name and stop looking 4694 $FoundFileName = $Dir.$File;
4700 # save location in cache 4701 $this->TemplateLocationCache[$CacheIndex]
4704 # set flag indicating that cache should be saved 4705 $this->SaveTemplateLocationCache = TRUE;
4708 # return full template file name to caller 4709 return $FoundFileName;
4718 private function ExpandDirectoryList($DirList)
4720 # generate lookup for supplied list 4721 $ExpandedListKey = md5(serialize($DirList)
4722 .self::$DefaultUI.self::$ActiveUI);
4724 # if we already have expanded version of supplied list 4725 if (isset($this->ExpandedDirectoryLists[$ExpandedListKey]))
4727 # return expanded version to caller 4728 return $this->ExpandedDirectoryLists[$ExpandedListKey];
4731 # for each directory in list 4732 $ExpDirList = array();
4733 foreach ($DirList as $Dir)
4735 # if directory includes substitution keyword 4736 if ((strpos($Dir,
"%DEFAULTUI%") !== FALSE)
4737 || (strpos($Dir,
"%ACTIVEUI%") !== FALSE))
4739 # start with empty new list segment 4740 $ExpDirListSegment = array();
4742 # use default values for initial parent 4743 $ParentInterface = array(self::$ActiveUI, self::$DefaultUI);
4747 # substitute in for keyword on parent 4748 $CurrDir = str_replace(array(
"%ACTIVEUI%",
"%DEFAULTUI%"),
4749 $ParentInterface, $Dir);
4751 # add local version of parent directory to new list segment 4752 $ExpDirListSegment[] =
"local/".$CurrDir;
4754 # add parent directory to new list segment 4755 $ExpDirListSegment[] = $CurrDir;
4757 # look for new parent interface 4758 $ParentInterface = $this->GetInterfaceSetting(
4759 $CurrDir,
"ParentInterface");
4761 # repeat if parent is available 4762 }
while (strlen($ParentInterface));
4764 # add new list segment to expanded list 4765 $ExpDirList = array_merge($ExpDirList, $ExpDirListSegment);
4769 # add local version of directory to expanded list 4770 $ExpDirList[] =
"local/".$Dir;
4772 # add directory to expanded list 4773 $ExpDirList[] = $Dir;
4777 # return expanded version to caller 4778 $this->ExpandedDirectoryLists[$ExpandedListKey] = $ExpDirList;
4779 return $this->ExpandedDirectoryLists[$ExpandedListKey];
4790 private function GetInterfaceSetting(
4791 $InterfaceDir, $SettingName = NULL)
4793 # extract canonical interface name and base interface directory 4794 preg_match(
"%(.*interface/)([^/]+)%", $InterfaceDir, $Matches);
4795 $InterfaceDir = (count($Matches) > 2)
4796 ? $Matches[1].$Matches[2] : $InterfaceDir;
4797 $InterfaceName = (count($Matches) > 2)
4798 ? $Matches[2] :
"UNKNOWN";
4800 # if we do not have settings for interface 4801 if (!isset($this->InterfaceSettings[$InterfaceName]))
4803 # load default values for settings 4804 $this->InterfaceSettings[$InterfaceName] = array(
4809 # if directory takes precedence over existing settings source 4810 # ("takes precendence" == is more local == longer directory length) 4811 if (strlen($InterfaceDir)
4812 > strlen($this->InterfaceSettings[$InterfaceName][
"Source"]))
4814 # if settings file exists in directory 4815 $SettingsFile = $InterfaceDir.
"/interface.ini";
4816 if (is_readable($SettingsFile))
4818 # read in values from file 4819 $NewSettings = parse_ini_file($SettingsFile);
4821 # merge in values with existing settings 4822 $this->InterfaceSettings[$InterfaceName] = array_merge(
4823 $this->InterfaceSettings[$InterfaceName], $NewSettings);
4825 # save new source of settings 4826 $this->InterfaceSettings[$InterfaceName][
"Source"] = $InterfaceDir;
4830 # return interface settings to caller 4832 ? (isset($this->InterfaceSettings[$InterfaceName][$SettingName])
4833 ? $this->InterfaceSettings[$InterfaceName][$SettingName]
4835 : $this->InterfaceSettings[$InterfaceName];
4846 private function CompileScssFile($SrcFile)
4848 # build path to CSS file 4849 $DstFile = self::$ScssCacheDir.
"/".dirname($SrcFile)
4850 .
"/".basename($SrcFile);
4851 $DstFile = substr_replace($DstFile,
"css", -4);
4853 # if SCSS file is newer than CSS file 4854 if (!file_exists($DstFile)
4855 || (filemtime($SrcFile) > filemtime($DstFile)))
4857 # attempt to create CSS cache subdirectory if not present 4858 if (!is_dir(dirname($DstFile)))
4860 @mkdir(dirname($DstFile), 0777, TRUE);
4863 # if CSS cache directory and CSS file path appear writable 4864 static $CacheDirIsWritable;
4865 if (!isset($CacheDirIsWritable))
4866 { $CacheDirIsWritable = is_writable(self::$ScssCacheDir); }
4867 if (is_writable($DstFile)
4868 || (!file_exists($DstFile) && $CacheDirIsWritable))
4870 # load SCSS and compile to CSS 4871 $ScssCode = file_get_contents($SrcFile);
4872 $ScssCompiler =
new scssc();
4874 ?
"scss_formatter_compressed" :
"scss_formatter");
4877 $CssCode = $ScssCompiler->compile($ScssCode);
4879 # add fingerprinting for URLs in CSS 4880 $this->CssUrlFingerprintPath = dirname($SrcFile);
4881 $CssCode = preg_replace_callback(
4882 "/url\((['\"]*)(.+)\.([a-z]+)(['\"]*)\)/",
4883 array($this,
"CssUrlFingerprintInsertion"),
4886 # strip out comments from CSS (if requested) 4889 $CssCode = preg_replace(
'!/\*[^*]*\*+([^/][^*]*\*+)*/!',
4893 # write out CSS file 4894 file_put_contents($DstFile, $CssCode);
4896 catch (Exception $Ex)
4898 $this->
LogError(self::LOGLVL_ERROR,
4899 "Error compiling SCSS file ".$SrcFile.
": " 4900 .$Ex->getMessage());
4906 # log error and set CSS file path to indicate failure 4907 $this->
LogError(self::LOGLVL_ERROR,
4908 "Unable to write out CSS file (compiled from SCSS) to " 4914 # return CSS file path to caller 4925 private function MinimizeJavascriptFile($SrcFile)
4927 # bail out if file is on exclusion list 4928 foreach ($this->DoNotMinimizeList as $DNMFile)
4930 if (($SrcFile == $DNMFile) || (basename($SrcFile) == $DNMFile))
4936 # build path to minimized file 4937 $DstFile = self::$JSMinCacheDir.
"/".dirname($SrcFile)
4938 .
"/".basename($SrcFile);
4939 $DstFile = substr_replace($DstFile,
".min", -3, 0);
4941 # if original file is newer than minimized file 4942 if (!file_exists($DstFile)
4943 || (filemtime($SrcFile) > filemtime($DstFile)))
4945 # attempt to create cache subdirectory if not present 4946 if (!is_dir(dirname($DstFile)))
4948 @mkdir(dirname($DstFile), 0777, TRUE);
4951 # if cache directory and minimized file path appear writable 4952 static $CacheDirIsWritable;
4953 if (!isset($CacheDirIsWritable))
4954 { $CacheDirIsWritable = is_writable(self::$JSMinCacheDir); }
4955 if (is_writable($DstFile)
4956 || (!file_exists($DstFile) && $CacheDirIsWritable))
4958 # load JavaScript code 4959 $Code = file_get_contents($SrcFile);
4961 # decide which minimizer to use 4962 if ($this->JSMinimizerJavaScriptPackerAvailable
4963 && $this->JSMinimizerJShrinkAvailable)
4965 $Minimizer = (strlen($Code) < 5000)
4966 ?
"JShrink" :
"JavaScriptPacker";
4968 elseif ($this->JSMinimizerJShrinkAvailable)
4970 $Minimizer =
"JShrink";
4974 $Minimizer =
"NONE";
4980 case "JavaScriptMinimizer":
4982 $MinimizedCode = $Packer->pack();
4990 catch (Exception $Exception)
4992 unset($MinimizedCode);
4993 $MinimizeError = $Exception->getMessage();
4998 # if minimization succeeded 4999 if (isset($MinimizedCode))
5001 # write out minimized file 5002 file_put_contents($DstFile, $MinimizedCode);
5006 # log error and set destination file path to indicate failure 5007 $ErrMsg =
"Unable to minimize JavaScript file ".$SrcFile;
5008 if (isset($MinimizeError))
5010 $ErrMsg .=
" (".$MinimizeError.
")";
5012 $this->
LogError(self::LOGLVL_ERROR, $ErrMsg);
5018 # log error and set destination file path to indicate failure 5019 $this->
LogError(self::LOGLVL_ERROR,
5020 "Unable to write out minimized JavaScript to file ".$DstFile);
5025 # return CSS file path to caller 5036 private function CssUrlFingerprintInsertion($Matches)
5038 # generate fingerprint string from CSS file modification time 5039 $FileName = realpath($this->CssUrlFingerprintPath.
"/".
5040 $Matches[2].
".".$Matches[3]);
5041 $MTime = filemtime($FileName);
5042 $Fingerprint = sprintf(
"%06X", ($MTime % 0xFFFFFF));
5044 # build URL string with fingerprint and return it to caller 5045 return "url(".$Matches[1].$Matches[2].
".".$Fingerprint
5046 .
".".$Matches[3].$Matches[4].
")";
5056 private function GetRequiredFilesNotYetLoaded($PageContentFile)
5058 # start out assuming no files required 5059 $RequiredFiles = array();
5061 # if page content file supplied 5062 if ($PageContentFile)
5064 # if file containing list of required files is available 5065 $Path = dirname($PageContentFile);
5066 $RequireListFile = $Path.
"/REQUIRES";
5067 if (file_exists($RequireListFile))
5069 # read in list of required files 5070 $RequestedFiles = file($RequireListFile);
5072 # for each line in required file list 5073 foreach ($RequestedFiles as $Line)
5075 # if line is not a comment 5076 $Line = trim($Line);
5077 if (!preg_match(
"/^#/", $Line))
5079 # if file has not already been loaded 5080 if (!in_array($Line, $this->FoundUIFiles))
5082 # add to list of required files 5083 $RequiredFiles[$Line] = self::ORDER_MIDDLE;
5090 # add in additional required files if any 5091 if (count($this->AdditionalRequiredUIFiles))
5093 # make sure there are no duplicates 5094 $AdditionalRequiredUIFiles = array_unique(
5095 $this->AdditionalRequiredUIFiles);
5097 $RequiredFiles = array_merge(
5098 $RequiredFiles, $this->AdditionalRequiredUIFiles);
5101 # return list of required files to caller 5102 return $RequiredFiles;
5113 private function SubBrowserIntoFileNames($FileNames)
5115 # if a browser detection function has been made available 5116 $UpdatedFileNames = array();
5117 if (is_callable($this->BrowserDetectFunc))
5119 # call function to get browser list 5120 $Browsers = call_user_func($this->BrowserDetectFunc);
5122 # for each required file 5123 foreach ($FileNames as $FileName => $Value)
5125 # if file name includes browser keyword 5126 if (preg_match(
"/%BROWSER%/", $FileName))
5129 foreach ($Browsers as $Browser)
5131 # substitute in browser name and add to new file list 5132 $NewFileName = preg_replace(
5133 "/%BROWSER%/", $Browser, $FileName);
5134 $UpdatedFileNames[$NewFileName] = $Value;
5139 # add to new file list 5140 $UpdatedFileNames[$FileName] = $Value;
5146 # filter out any files with browser keyword in their name 5147 foreach ($FileNames as $FileName => $Value)
5149 if (!preg_match(
"/%BROWSER%/", $FileName))
5151 $UpdatedFileNames[$FileName] = $Value;
5156 return $UpdatedFileNames;
5164 private function AddMetaTagsToPageOutput($PageOutput)
5166 # start with unconditional (non-unique) tags 5167 $TagsToAdd = $this->MetaTags;
5169 # for each unique tag 5170 foreach ($this->UniqueMetaTags as $UniqueMetaTag)
5172 $Attribs = $UniqueMetaTag[
"Attribs"];
5173 $UniqueAttribs = $UniqueMetaTag[
"UniqueAttribs"];
5175 # if no unique attributes specified 5176 if ($UniqueAttribs === NULL)
5178 # use first attribute as unique attribute 5179 $UniqueAttribs = array_slice($Attribs, 0, 1);
5182 # for each already-queued tag 5183 # (look for meta tags that match all attributes in 5184 # the current unique tag) 5185 foreach ($TagsToAdd as $TagAttribs)
5187 # for each attribute in current unique tag 5188 # (look for attributes in the current unique tag that do 5189 # not match attributes in the this queued tag) 5190 foreach ($UniqueAttribs as $UniqueName => $UniqueValue)
5192 # if unique attribute is not found in queued tag 5193 # or queued tag attribute has a different value 5194 if (!isset($TagAttribs[$UniqueName])
5195 || ($TagAttribs[$UniqueName] != $UniqueValue))
5197 # skip to next queued tag 5198 # (some attribute in the current unique tag 5199 # was not found in the queued tag) 5204 # skip to next unique tag 5205 # (all attributes in the current unique tag were found 5206 # in the queued tag, so do not queue this unique tag) 5210 # generate potential combinations of unique attributes 5212 array_keys($UniqueAttribs));
5214 # for each combination of unique attributes 5215 foreach ($UniqueAttribNameCombos as $UniqueNameCombo)
5217 # for each attribute in combination 5218 $AttribStrings = array();
5219 foreach ($UniqueNameCombo as $UniqueName)
5221 # add attrib/value string to list 5222 $AttribStrings[] = $UniqueName.
"=\"" 5223 .htmlspecialchars($UniqueAttribs[$UniqueName]).
"\"";
5226 # build search string from list of attribute pairs 5227 $SearchString =
"<meta ".implode(
" ", $AttribStrings);
5229 # if search string appears in page output 5230 if (strpos($PageOutput, $SearchString) !== FALSE)
5232 # skip to next unique tag 5236 # repeat search with single quotes instead of double quotes 5237 $SearchString = strtr($SearchString,
'"',
"'");
5238 if (strpos($PageOutput, $SearchString) !== FALSE)
5240 # skip to next unique tag 5245 # unique tag was not found in page output, so add it to inserted tags 5246 $TagsToAdd[] = $Attribs;
5249 # if there are meta tags to be added 5250 if (count($TagsToAdd))
5252 # start with an empty segment 5256 foreach ($TagsToAdd as $Attribs)
5258 # assemble tag and add it to the segment 5259 $Section .=
"<meta";
5260 foreach ($Attribs as $AttribName => $AttribValue)
5262 $Section .=
" ".$AttribName.
"=\"" 5263 .htmlspecialchars(trim($AttribValue)).
"\"";
5265 $Section .=
" />\n";
5268 # if standard page start and end have been disabled 5269 if ($this->SuppressStdPageStartAndEnd)
5271 # add segment to beginning of page output 5272 $PageOutput = $Section.$PageOutput;
5276 # insert segment at beginning of HTML head section in page output 5277 $PageOutput = preg_replace(
"#<head>#i",
5278 "<head>\n".$Section, $PageOutput, 1);
5282 # return (potentially modified) page output to caller 5293 private function AddFileTagsToPageOutput($PageOutput, $Files)
5295 # substitute browser name into names of required files as appropriate 5296 $Files = $this->SubBrowserIntoFileNames($Files);
5298 # initialize content sections 5300 self::ORDER_FIRST =>
"",
5301 self::ORDER_MIDDLE =>
"",
5302 self::ORDER_LAST =>
"",
5305 self::ORDER_FIRST =>
"",
5306 self::ORDER_MIDDLE =>
"",
5307 self::ORDER_LAST =>
"",
5310 # for each required file 5311 foreach ($Files as $File => $Order)
5313 # locate specific file to use 5314 $FilePath = $this->
GUIFile($File);
5319 # generate tag for file 5320 $Tag = $this->GetUIFileLoadingTag($FilePath);
5322 # add file to HTML output based on file type 5327 $HeadContent[$Order] .= $Tag.
"\n";
5330 case self::FT_JAVASCRIPT:
5331 $BodyContent[$Order] .= $Tag.
"\n";
5337 # add content to head 5338 $Replacement = $HeadContent[self::ORDER_MIDDLE]
5339 .$HeadContent[self::ORDER_LAST];
5340 $UpdatedPageOutput = str_ireplace(
"</head>",
5341 $Replacement.
"</head>",
5342 $PageOutput, $ReplacementCount);
5343 # (if no </head> tag was found, just prepend tags to page content) 5344 if ($ReplacementCount == 0)
5346 $PageOutput = $Replacement.$PageOutput;
5348 # (else if multiple </head> tags found, only prepend tags to the first) 5349 elseif ($ReplacementCount > 1)
5351 $PageOutput = preg_replace(
"#</head>#i",
5352 $Replacement.
"</head>",
5357 $PageOutput = $UpdatedPageOutput;
5359 $Replacement = $HeadContent[self::ORDER_FIRST];
5360 $UpdatedPageOutput = str_ireplace(
"<head>",
5361 "<head>\n".$Replacement,
5362 $PageOutput, $ReplacementCount);
5363 # (if no <head> tag was found, just prepend tags to page content) 5364 if ($ReplacementCount == 0)
5366 $PageOutput = $Replacement.$PageOutput;
5368 # (else if multiple <head> tags found, only append tags to the first) 5369 elseif ($ReplacementCount > 1)
5371 $PageOutput = preg_replace(
"#<head>#i",
5372 "<head>\n".$Replacement,
5377 $PageOutput = $UpdatedPageOutput;
5380 # add content to body 5381 $Replacement = $BodyContent[self::ORDER_FIRST];
5382 $PageOutput = preg_replace(
"#<body([^>]*)>#i",
5383 "<body\\1>\n".$Replacement,
5384 $PageOutput, 1, $ReplacementCount);
5385 # (if no <body> tag was found, just append tags to page content) 5386 if ($ReplacementCount == 0)
5388 $PageOutput = $PageOutput.$Replacement;
5390 $Replacement = $BodyContent[self::ORDER_MIDDLE]
5391 .$BodyContent[self::ORDER_LAST];
5392 $UpdatedPageOutput = str_ireplace(
"</body>",
5393 $Replacement.
"\n</body>",
5394 $PageOutput, $ReplacementCount);
5395 # (if no </body> tag was found, just append tags to page content) 5396 if ($ReplacementCount == 0)
5398 $PageOutput = $PageOutput.$Replacement;
5400 # (else if multiple </body> tags found, only prepend tag to the first) 5401 elseif ($ReplacementCount > 1)
5403 $PageOutput = preg_replace(
"#</body>#i",
5404 $Replacement.
"\n</body>",
5409 $PageOutput = $UpdatedPageOutput;
5425 private function GetUIFileLoadingTag(
5426 $FileName, $AdditionalAttributes = NULL)
5428 # pad additional attributes if supplied 5429 $AddAttribs = $AdditionalAttributes ?
" ".$AdditionalAttributes :
"";
5431 # retrieve type of UI file 5434 # construct tag based on file type 5438 $Tag =
" <link rel=\"stylesheet\" type=\"text/css\"" 5439 .
" media=\"all\" href=\"".$FileName.
"\"" 5440 .$AddAttribs.
" />\n";
5443 case self::FT_JAVASCRIPT:
5444 $Tag =
" <script type=\"text/javascript\"" 5445 .
" src=\"".$FileName.
"\"" 5446 .$AddAttribs.
"></script>\n";
5449 case self::FT_IMAGE:
5450 $Tag =
"<img src=\"".$FileName.
"\"".$AddAttribs.
">";
5458 # return constructed tag to caller 5466 private function AutoloadObjects($ClassName)
5468 # if caching is not turned off 5469 # and we have a cached location for class 5470 # and file at cached location is readable 5471 if ((self::$ObjectLocationCacheInterval > 0)
5472 && array_key_exists($ClassName,
5473 self::$ObjectLocationCache)
5474 && is_readable(self::$ObjectLocationCache[$ClassName]))
5476 # use object location from cache 5477 require_once(self::$ObjectLocationCache[$ClassName]);
5481 # for each possible object file directory 5483 foreach (self::$ObjectDirectories as $Location => $Info)
5485 # make any needed replacements in directory path 5486 $Location = str_replace(array(
"%ACTIVEUI%",
"%DEFAULTUI%"),
5487 array(self::$ActiveUI, self::$DefaultUI), $Location);
5489 # if directory looks valid 5490 if (is_dir($Location))
5492 # pass class name through callback (if supplied) 5493 $ClassFileName = $ClassName;
5494 if (is_callable($Info[
"Callback"]))
5496 $ClassFileName = $Info[
"Callback"]($ClassFileName);
5499 # strip off any namespace prefix 5500 foreach ($Info[
"NamespacePrefixes"] as $Prefix)
5502 if (strpos($ClassFileName, $Prefix) === 0)
5504 $ClassFileName = substr($ClassFileName, strlen($Prefix));
5509 # strip off any leading namespace separator 5510 if (strpos($ClassFileName,
"\\") === 0)
5512 $ClassFileName = substr($ClassFileName, 1);
5515 # convert any namespace separators to directory separators 5516 $ClassFileName = str_replace(
"\\",
"/", $ClassFileName);
5518 # finish building class file name 5519 $ClassFileName = $ClassFileName.
".php";
5521 # read in directory contents if not already retrieved 5522 if (!isset($FileLists[$Location]))
5524 $FileLists[$Location] = self::ReadDirectoryTree(
5525 $Location,
'/^.+\.php$/i');
5528 # for each file in target directory 5529 foreach ($FileLists[$Location] as $FileName)
5531 # if file matches our target object file name 5532 if ($FileName == $ClassFileName)
5534 # include object file 5535 require_once($Location.$FileName);
5537 # save location to cache 5538 self::$ObjectLocationCache[$ClassName]
5539 = $Location.$FileName;
5541 # set flag indicating that cache should be saved 5542 self::$SaveObjectLocationCache = TRUE;
5560 private static function ReadDirectoryTree($Directory, $Pattern)
5562 $CurrentDir = getcwd();
5564 $DirIter =
new RecursiveDirectoryIterator(
".");
5565 $IterIter =
new RecursiveIteratorIterator($DirIter);
5566 $RegexResults =
new RegexIterator($IterIter, $Pattern,
5567 RecursiveRegexIterator::GET_MATCH);
5568 $FileList = array();
5569 foreach ($RegexResults as $Result)
5571 $FileList[] = substr($Result[0], 2);
5581 private function LoadUIFunctions()
5584 "local/interface/%ACTIVEUI%/include",
5585 "interface/%ACTIVEUI%/include",
5586 "local/interface/%DEFAULTUI%/include",
5587 "interface/%DEFAULTUI%/include",
5589 foreach ($Dirs as $Dir)
5591 $Dir = str_replace(array(
"%ACTIVEUI%",
"%DEFAULTUI%"),
5592 array(self::$ActiveUI, self::$DefaultUI), $Dir);
5595 $FileNames = scandir($Dir);
5596 foreach ($FileNames as $FileName)
5598 if (preg_match(
"/^F-([A-Za-z0-9_]+)\.php/",
5599 $FileName, $Matches)
5600 || preg_match(
"/^F-([A-Za-z0-9_]+)\.html/",
5601 $FileName, $Matches))
5603 if (!function_exists($Matches[1]))
5605 include_once($Dir.
"/".$FileName);
5618 private function ProcessPeriodicEvent($EventName, $Callback)
5620 # retrieve last execution time for event if available 5621 $Signature = self::GetCallbackSignature($Callback);
5622 $LastRun = $this->DB->Query(
"SELECT LastRunAt FROM PeriodicEvents" 5623 .
" WHERE Signature = '".addslashes($Signature).
"'",
"LastRunAt");
5625 # determine whether enough time has passed for event to execute 5626 $ShouldExecute = (($LastRun === NULL)
5627 || (time() > (strtotime($LastRun) + $this->EventPeriods[$EventName])))
5630 # if event should run 5633 # add event to task queue 5634 $WrapperCallback = array(
"ApplicationFramework",
"RunPeriodicEvent");
5635 $WrapperParameters = array(
5636 $EventName, $Callback, array(
"LastRunAt" => $LastRun));
5640 # add event to list of periodic events 5641 $this->KnownPeriodicEvents[$Signature] = array(
5642 "Period" => $EventName,
5643 "Callback" => $Callback,
5644 "Queued" => $ShouldExecute);
5652 private static function GetCallbackSignature($Callback)
5654 return !is_array($Callback) ? $Callback
5655 : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
5663 private function PrepForTSR()
5665 # if HTML has been output and it's time to launch another task 5666 # (only TSR if HTML has been output because otherwise browsers 5667 # may misbehave after connection is closed) 5668 if ((PHP_SAPI !=
"cli")
5669 && ($this->JumpToPage || !$this->SuppressHTML)
5670 && !$this->LoadedViaAlternateDomain()
5671 && $this->
TaskMgr->TaskExecutionEnabled()
5672 && $this->
TaskMgr->GetTaskQueueSize())
5674 # begin buffering output for TSR 5677 # let caller know it is time to launch another task 5682 # let caller know it is not time to launch another task 5691 private function LaunchTSR()
5693 # set headers to close out connection to browser 5696 ignore_user_abort(TRUE);
5697 header(
"Connection: close");
5698 header(
"Content-Length: ".ob_get_length());
5701 # output buffered content 5702 while (ob_get_level()) { ob_end_flush(); }
5705 # write out any outstanding data and end HTTP session 5706 session_write_close();
5708 # set flag indicating that we are now running in background 5709 $this->RunningInBackground = TRUE;
5711 # set database slow query threshold for background execution 5713 self::MIN_DB_SLOW_QUERY_THRESHOLD,
5714 self::DatabaseSlowQueryThresholdForBackground()));
5716 # handle garbage collection for session data 5717 if (isset($this->SessionStorage) &&
5718 (rand()/getrandmax()) <= $this->SessionGcProbability)
5720 # determine when sessions will expire 5721 $ExpiredTime = strtotime(
"-". self::$SessionLifetime.
" seconds");
5723 # iterate over files in the session directory with a DirectoryIterator 5724 # NB: we cannot use scandir() here because it reads the 5725 # entire list of files into memory and may exceed the memory 5726 # limit for directories with very many files 5727 $DI =
new DirectoryIterator($this->SessionStorage);
5728 while ($DI->valid())
5730 if ((strpos($DI->getFilename(),
"sess_") === 0) &&
5732 $DI->getCTime() < $ExpiredTime)
5734 unlink($DI->getPathname());
5741 # run qny queued tasks 5742 $this->
TaskMgr->RunQueuedTasks();
5752 # attempt to remove any memory limits 5754 ini_set(
"memory_limit", -1);
5756 # if there is a background task currently running 5757 if (isset($this->RunningTask))
5759 # add info about current page load 5761 $CrashInfo[
"FreeMemory"] = $FreeMemory;
5762 $CrashInfo[
"REMOTE_ADDR"] = $_SERVER[
"REMOTE_ADDR"];
5763 $CrashInfo[
"REQUEST_URI"] = $_SERVER[
"REQUEST_URI"];
5764 if (isset($_SERVER[
"REQUEST_TIME"]))
5766 $CrashInfo[
"REQUEST_TIME"] = $_SERVER[
"REQUEST_TIME"];
5768 if (isset($_SERVER[
"REMOTE_HOST"]))
5770 $CrashInfo[
"REMOTE_HOST"] = $_SERVER[
"REMOTE_HOST"];
5773 # add info about error that caused crash (if available) 5774 if (function_exists(
"error_get_last"))
5776 $CrashInfo[
"LastError"] = error_get_last();
5779 # add info about current output buffer contents (if available) 5780 if (ob_get_length() !== FALSE)
5782 $CrashInfo[
"OutputBuffer"] = ob_get_contents();
5785 # if backtrace info is available for the crash 5786 $Backtrace = debug_backtrace();
5787 if (count($Backtrace) > 1)
5789 # discard the current context from the backtrace 5790 array_shift($Backtrace);
5792 # add the backtrace to the crash info 5793 $CrashInfo[
"Backtrace"] = $Backtrace;
5795 # else if saved backtrace info is available 5796 elseif (isset($this->SavedContext))
5798 # add the saved backtrace to the crash info 5799 $CrashInfo[
"Backtrace"] = $this->SavedContext;
5802 # save crash info for currently running task 5804 $DB->Query(
"UPDATE RunningTasks SET CrashInfo = '" 5805 .addslashes(serialize($CrashInfo))
5806 .
"' WHERE TaskId = ".intval($this->RunningTask[
"TaskId"]));
5830 private function AddToDirList(
5831 $DirList, $Dir, $SearchLast, $SkipSlashCheck)
5833 # convert incoming directory to array of directories (if needed) 5834 $Dirs = is_array($Dir) ? $Dir : array($Dir);
5836 # reverse array so directories are searched in specified order 5837 $Dirs = array_reverse($Dirs);
5839 # for each directory 5840 foreach ($Dirs as $Location)
5842 # make sure directory includes trailing slash 5843 if (!$SkipSlashCheck)
5845 $Location = $Location
5846 .((substr($Location, -1) !=
"/") ?
"/" :
"");
5849 # remove directory from list if already present 5850 if (in_array($Location, $DirList))
5852 $DirList = array_diff(
5853 $DirList, array($Location));
5856 # add directory to list of directories 5859 array_push($DirList, $Location);
5863 array_unshift($DirList, $Location);
5867 # return updated directory list to caller 5877 private function OutputModificationCallbackShell($Matches)
5879 # call previously-stored external function 5880 return call_user_func($this->OutputModificationCallbackInfo[
"Callback"],
5882 $this->OutputModificationCallbackInfo[
"Pattern"],
5883 $this->OutputModificationCallbackInfo[
"Page"],
5884 $this->OutputModificationCallbackInfo[
"SearchPattern"]);
5895 private function CheckOutputModification(
5896 $Original, $Modified, $ErrorInfo)
5898 # if error was reported by regex engine 5899 if (preg_last_error() !== PREG_NO_ERROR)
5902 $this->
LogError(self::LOGLVL_ERROR,
5903 "Error reported by regex engine when modifying output." 5904 .
" (".$ErrorInfo.
")");
5906 # use unmodified version of output 5907 $OutputToUse = $Original;
5909 # else if modification reduced output by more than threshold 5910 elseif ((strlen(trim($Modified)) / strlen(trim($Original)))
5911 < self::OUTPUT_MODIFICATION_THRESHOLD)
5914 $this->
LogError(self::LOGLVL_WARNING,
5915 "Content reduced below acceptable threshold while modifying output." 5916 .
" (".$ErrorInfo.
")");
5918 # use unmodified version of output 5919 $OutputToUse = $Original;
5923 # use modified version of output 5924 $OutputToUse = $Modified;
5927 # return output to use to caller 5928 return $OutputToUse;
5943 private function UpdateSetting(
5944 $FieldName, $NewValue =
DB_NOVALUE, $Persistent = TRUE)
5946 static $LocalSettings;
5951 $LocalSettings[$FieldName] = $this->DB->UpdateValue(
5952 "ApplicationFrameworkSettings",
5953 $FieldName, $NewValue, NULL, $this->Settings);
5957 $LocalSettings[$FieldName] = $NewValue;
5960 elseif (!isset($LocalSettings[$FieldName]))
5962 $LocalSettings[$FieldName] = $this->DB->UpdateValue(
5963 "ApplicationFrameworkSettings",
5964 $FieldName, $NewValue, NULL, $this->Settings);
5966 return $LocalSettings[$FieldName];
5978 private static function IncludeFile(
5979 $_AF_File, $_AF_ContextVars = array())
5982 foreach ($_AF_ContextVars as $_AF_VarName => $_AF_VarValue)
5984 $$_AF_VarName = $_AF_VarValue;
5986 unset($_AF_VarName);
5987 unset($_AF_VarValue);
5988 unset($_AF_ContextVars);
5990 # add variables to context that we assume are always available 5991 $AF = $GLOBALS[
"AF"];
5996 # return updated context 5997 $ContextVars = get_defined_vars();
5998 unset($ContextVars[
"_AF_File"]);
5999 return $ContextVars;
6008 private function FilterContext($Context, $ContextVars)
6010 # clear all variables if no setting for context is available 6011 # or setting is FALSE 6012 if (!isset($this->ContextFilters[$Context])
6013 || ($this->ContextFilters[$Context] == FALSE))
6017 # keep all variables if setting for context is TRUE 6018 elseif ($this->ContextFilters[$Context] == TRUE)
6020 return $ContextVars;
6024 $Prefixes = $this->ContextFilters[$Context];
6025 $FilterFunc =
function($VarName) use ($Prefixes) {
6026 foreach ($Prefixes as $Prefix)
6028 if (substr($VarName, $Prefix) === 0)
6035 return array_filter(
6036 $ContextVars, $FilterFunc, ARRAY_FILTER_USE_KEY);
6041 private $InterfaceDirList = array(
6042 "interface/%ACTIVEUI%/",
6043 "interface/%DEFAULTUI%/",
6049 private $IncludeDirList = array(
6050 "interface/%ACTIVEUI%/include/",
6051 "interface/%ACTIVEUI%/objects/",
6052 "interface/%DEFAULTUI%/include/",
6053 "interface/%DEFAULTUI%/objects/",
6056 private $ImageDirList = array(
6057 "interface/%ACTIVEUI%/images/",
6058 "interface/%DEFAULTUI%/images/",
6061 private $FunctionDirList = array(
6062 "interface/%ACTIVEUI%/include/",
6063 "interface/%DEFAULTUI%/include/",
6067 private $ExpandedDirectoryLists = array();
6069 const NOVALUE =
".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
6072 # ---- Page Caching (Internal Methods) ----------------------------------- 6079 private function CheckForCachedPage($PageName)
6081 # assume no cached page will be found 6084 # if returning a cached page is allowed 6085 if ($this->CacheCurrentPage)
6087 # get fingerprint for requested page 6088 $PageFingerprint = $this->GetPageFingerprint($PageName);
6090 # look for matching page in cache in database 6091 $this->DB->Query(
"SELECT * FROM AF_CachedPages" 6092 .
" WHERE Fingerprint = '".addslashes($PageFingerprint).
"'");
6094 # if matching page found 6095 if ($this->DB->NumRowsSelected())
6097 # if cached page has expired 6098 $Row = $this->DB->FetchRow();
6099 $ExpirationTime = strtotime(
6101 if (strtotime($Row[
"CachedAt"]) < $ExpirationTime)
6103 # clear expired pages from cache 6104 $ExpirationTimestamp = date(
"Y-m-d H:i:s", $ExpirationTime);
6105 $this->DB->Query(
"DELETE CP, CPTI FROM AF_CachedPages CP," 6106 .
" AF_CachedPageTagInts CPTI" 6107 .
" WHERE CP.CachedAt < '".$ExpirationTimestamp.
"'" 6108 .
" AND CPTI.CacheId = CP.CacheId");
6109 $this->DB->Query(
"DELETE FROM AF_CachedPages " 6110 .
" WHERE CachedAt < '".$ExpirationTimestamp.
"'");
6114 # display cached page and exit 6115 $CachedPage = $Row[
"PageContent"];
6120 # return any cached page found to caller 6129 private function UpdatePageCache($PageName, $PageContent)
6131 # if page caching is enabled and current page should be cached 6133 && $this->CacheCurrentPage)
6135 # if page content looks invalid 6136 if (strlen(trim(strip_tags($PageContent))) == 0)
6139 $LogMsg =
"Page not cached because content was empty." 6140 .
" (PAGE: ".$PageName.
", URL: ".$this->
FullUrl().
")";
6141 $this->
LogError(self::LOGLVL_ERROR, $LogMsg);
6145 # save page to cache 6146 $PageFingerprint = $this->GetPageFingerprint($PageName);
6147 $this->DB->Query(
"INSERT INTO AF_CachedPages" 6148 .
" (Fingerprint, PageContent) VALUES" 6149 .
" ('".$this->DB->EscapeString($PageFingerprint).
"', '" 6150 .$this->DB->EscapeString($PageContent).
"')");
6151 $CacheId = $this->DB->LastInsertId();
6153 # for each page cache tag that was added 6154 foreach ($this->PageCacheTags as $Tag => $Pages)
6156 # if current page is in list for tag 6157 if (in_array(
"CURRENT", $Pages) || in_array($PageName, $Pages))
6160 $TagId = $this->GetPageCacheTagId($Tag);
6162 # mark current page as associated with tag 6163 $this->DB->Query(
"INSERT INTO AF_CachedPageTagInts" 6164 .
" (TagId, CacheId) VALUES " 6165 .
" (".intval($TagId).
", ".intval($CacheId).
")");
6177 private function GetPageCacheTagId($Tag)
6179 # if tag is a non-negative integer 6180 if (is_numeric($Tag) && ($Tag > 0) && (intval($Tag) == $Tag))
6183 $Id = self::PAGECACHETAGIDOFFSET + $Tag;
6187 # look up ID in database 6188 $Id = $this->DB->Query(
"SELECT TagId FROM AF_CachedPageTags" 6189 .
" WHERE Tag = '".addslashes($Tag).
"'",
"TagId");
6191 # if ID was not found 6194 # add tag to database 6195 $this->DB->Query(
"INSERT INTO AF_CachedPageTags" 6196 .
" SET Tag = '".addslashes($Tag).
"'");
6197 $Id = $this->DB->LastInsertId();
6201 # return tag ID to caller 6210 private function GetPageFingerprint($PageName)
6212 # only get the environmental fingerprint once so that it is consistent 6213 # between page construction start and end 6214 static $EnvFingerprint;
6215 if (!isset($EnvFingerprint))
6221 $EnvData = json_encode($GetVars)
6222 .json_encode($PostVars)
6223 .$_SERVER[
"SERVER_NAME"];
6224 $EnvFingerprint = md5($EnvData);
6227 # build page fingerprint and return it to caller 6228 return $PageName.
"-".$EnvFingerprint;
6237 private function UpdateLastUsedTimeForActiveSessions()
6241 $_SESSION[
"AF_SessionLastUsed"] = date(
"Y-m-d H:i:s");
6243 elseif (isset($_SESSION[
"AF_SessionLastUsed"]))
6245 unset($_SESSION[
"AF_SessionLastUsed"]);
6256 private function AdjustEnvironmentForCgi()
6258 # if it appears we are running via a CGI interpreter 6259 if (isset($_SERVER[
"ORIG_SCRIPT_NAME"]))
6261 # for each server environment variable 6262 foreach ($_SERVER as $Key => $Value)
6264 # if variable appears the result of using CGI 6265 if (strpos($Key,
"REDIRECT_") === 0)
6267 # if unmodified version of variable is not set 6268 $KeyWithoutPrefix = substr($Key, 9);
6269 if (!isset($_SERVER[$KeyWithoutPrefix]))
6271 # set unmodified version of variable 6272 $_SERVER[$KeyWithoutPrefix] = $_SERVER[$Key];
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.
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...
DatabaseSlowQueryThresholdForBackground($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set threshold for when database queries are considered "slow" when running in the background...
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.
MaxExecutionTime($NewValue=NULL, $Persistent=FALSE)
Get/set maximum PHP execution time.
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.
GetPageCachePageList()
Get list of cached pages.
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.
DatabaseSlowQueryThresholdForForeground($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set threshold for when database queries are considered "slow" when running in the foreground...
LogHighMemoryUsage($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether logging of high memory usage is enabled.
static SlowQueryThreshold($NewValue=NULL)
Get/set current threshold for what is considered a "slow" SQL query.
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.
static GetPercentFreeMemory()
Get current percentage of free memory.
const MIN_DB_SLOW_QUERY_THRESHOLD
minimum threshold for what is considered a slow database query
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.
static SuppressSessionInitialization($NewValue=NULL)
Get/set whether session initialization is intentionally suppressed.
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.
ObjectLocationCacheExpirationInterval($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set object file location cache expiration period in minutes.
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.
Task manager component of top-level framework for web applications.
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).
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.
& TaskMgr()
Access the task manager.
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.