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 14 class ApplicationFramework
17 # ---- PUBLIC INTERFACE -------------------------------------------------- 25 public function __construct()
27 # save execution start time 28 $this->ExecutionStartTime = microtime(TRUE);
30 # begin/restore PHP session 31 $SessionDomain = isset($_SERVER[
"SERVER_NAME"]) ? $_SERVER[
"SERVER_NAME"]
32 : isset($_SERVER[
"HTTP_HOST"]) ? $_SERVER[
"HTTP_HOST"]
34 if (is_writable(session_save_path()))
36 $SessionStorage = session_save_path()
37 .
"/".self::$AppName.
"_".md5($SessionDomain.dirname(__FILE__));
38 if (!is_dir($SessionStorage)) { mkdir($SessionStorage, 0700 ); }
39 if (is_writable($SessionStorage))
41 # save params of our session storage as instance variables for later use 42 $this->SessionGcProbability =
43 ini_get(
"session.gc_probability") / ini_get(
"session.gc_divisor");
44 $this->SessionStorage = $SessionStorage;
46 # store our session files in a subdir to avoid 47 # accidentally sharing sessions with other CWIS installs 49 session_save_path($SessionStorage);
51 # set PHP's gc not to run, as it doesn't handle subdirectories anyway 52 # instead, we'll do the cleanup as we run background tasks 53 ini_set(
"session.gc_probability", 0);
56 ini_set(
"session.gc_maxlifetime", self::$SessionLifetime);
57 session_set_cookie_params(
58 self::$SessionLifetime,
"/", $SessionDomain);
59 if (php_sapi_name() !==
"cli")
64 # set up default object file search locations 65 self::AddObjectDirectory(
"local/interface/%ACTIVEUI%/objects");
66 self::AddObjectDirectory(
"interface/%ACTIVEUI%/objects");
67 self::AddObjectDirectory(
"local/interface/%DEFAULTUI%/objects");
68 self::AddObjectDirectory(
"interface/%DEFAULTUI%/objects");
69 self::AddObjectDirectory(
"local/objects");
70 self::AddObjectDirectory(
"objects");
72 # set up object file autoloader 73 spl_autoload_register(
"ApplicationFramework::AutoloadObjects");
75 # set up function to output any buffered text in case of crash 76 register_shutdown_function(array($this,
"OnCrash"));
78 # set up our internal environment 81 # set up our exception handler 82 set_exception_handler(array($this,
"GlobalExceptionHandler"));
84 # perform any work needed to undo PHP magic quotes 85 $this->UndoMagicQuotes();
87 # load our settings from database 88 $this->LoadSettings();
90 # set PHP maximum execution time 91 $this->MaxExecutionTime($this->Settings[
"MaxExecTime"]);
93 # register events we handle internally 94 $this->RegisterEvent($this->PeriodicEvents);
95 $this->RegisterEvent($this->UIEvents);
97 # attempt to create SCSS cache directory if needed and it does not exist 98 if ($this->ScssSupportEnabled() && !is_dir(self::$ScssCacheDir))
99 { @mkdir(self::$ScssCacheDir, 0777, TRUE); }
101 # attempt to create minimized JS cache directory if needed and it does not exist 102 if ($this->UseMinimizedJavascript()
103 && $this->JavascriptMinimizationEnabled()
104 && !is_dir(self::$JSMinCacheDir))
106 @mkdir(self::$JSMinCacheDir, 0777, TRUE);
115 public function __destruct()
117 # if template location cache is flagged to be saved 118 if ($this->SaveTemplateLocationCache)
120 # write template location cache out and update cache expiration 121 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings" 122 .
" SET TemplateLocationCache = '" 123 .addslashes(serialize(
124 $this->TemplateLocationCache)).
"'," 125 .
" TemplateLocationCacheExpiration = '" 127 $this->TemplateLocationCacheExpiration).
"'");
130 # if object location cache is flagged to be saved 131 if (self::$SaveObjectLocationCache)
133 # write object location cache out and update cache expiration 134 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings" 135 .
" SET ObjectLocationCache = '" 136 .addslashes(serialize(
137 self::$ObjectLocationCache)).
"'," 138 .
" ObjectLocationCacheExpiration = '" 140 self::$ObjectLocationCacheExpiration).
"'");
150 public function GlobalExceptionHandler($Exception)
152 # display exception info 153 $Location = str_replace(getcwd().
"/",
"",
154 $Exception->getFile().
"[".$Exception->getLine().
"]");
155 ?><table width=
"100%" cellpadding=
"5" 156 style=
"border: 2px solid #666666; background: #CCCCCC; 157 font-family: Courier New, Courier, monospace; 158 margin-top: 10px;"><tr><td>
159 <div style=
"color: #666666;">
160 <span style=
"font-size: 150%;">
161 <b>Uncaught Exception</b></span><br />
162 <b>
Message:</b> <i><?
PHP print $Exception->getMessage(); ?></i><br />
163 <b>Location:</b> <i><?
PHP print $Location; ?></i><br />
165 <blockquote><pre><?
PHP print preg_replace(
166 ":(#[0-9]+) ".getcwd().
"/".
":",
"$1 ",
167 $Exception->getTraceAsString());
168 ?></pre></blockquote>
170 </td></tr></table><?
PHP 172 # log exception if possible 173 $TraceString = $Exception->getTraceAsString();
174 $TraceString = str_replace(
"\n",
", ", $TraceString);
175 $TraceString = preg_replace(
":(#[0-9]+) ".getcwd().
"/".
":",
"$1 ", $TraceString);
176 $LogMsg =
"Uncaught exception (".$Exception->getMessage().
") at " 177 .$Location.
". TRACE: ".$TraceString.
" URL: ".$this->FullUrl();
178 $this->LogError(self::LOGLVL_ERROR, $LogMsg);
196 public static function AddObjectDirectory(
197 $Dir, $Prefix =
"", $ClassPattern = NULL, $ClassReplacement = NULL)
199 # make sure directory has trailing slash 200 $Dir = $Dir.((substr($Dir, -1) !=
"/") ?
"/" :
"");
202 # add directory to directory list 203 self::$ObjectDirectories[$Dir] = array(
205 "ClassPattern" => $ClassPattern,
206 "ClassReplacement" => $ClassReplacement,
229 public function AddImageDirectories(
230 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
232 # add directories to existing image directory list 233 $this->ImageDirList = $this->AddToDirList(
234 $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
257 public function AddIncludeDirectories(
258 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
260 # add directories to existing image directory list 261 $this->IncludeDirList = $this->AddToDirList(
262 $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
284 public function AddInterfaceDirectories(
285 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
287 # add directories to existing image directory list 288 $this->InterfaceDirList = $this->AddToDirList(
289 $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
311 public function AddFunctionDirectories(
312 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
314 # add directories to existing image directory list 315 $this->FunctionDirList = $this->AddToDirList(
316 $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
324 public function SetBrowserDetectionFunc($DetectionFunc)
326 $this->BrowserDetectFunc = $DetectionFunc;
335 public function AddUnbufferedCallback($Callback, $Parameters=array())
337 if (is_callable($Callback))
339 $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
349 public function TemplateLocationCacheExpirationInterval($NewInterval =
DB_NOVALUE)
351 return $this->UpdateSetting(
"TemplateLocationCacheInterval", $NewInterval);
357 public function ClearTemplateLocationCache()
359 $this->TemplateLocationCache = array();
360 $this->SaveTemplateLocationCache = TRUE;
369 public function ObjectLocationCacheExpirationInterval($NewInterval =
DB_NOVALUE)
371 return $this->UpdateSetting(
"ObjectLocationCacheInterval", $NewInterval);
377 public function ClearObjectLocationCache()
379 self::$ObjectLocationCache = array();
380 self::$SaveObjectLocationCache = TRUE;
389 public function UrlFingerprintingEnabled($NewValue =
DB_NOVALUE)
391 return $this->UpdateSetting(
"UrlFingerprintingEnabled", $NewValue);
401 public function ScssSupportEnabled($NewValue =
DB_NOVALUE)
403 return $this->UpdateSetting(
"ScssSupportEnabled", $NewValue);
414 public function GenerateCompactCss($NewValue =
DB_NOVALUE)
416 return $this->UpdateSetting(
"GenerateCompactCss", $NewValue);
427 public function UseMinimizedJavascript($NewValue =
DB_NOVALUE)
429 return $this->UpdateSetting(
"UseMinimizedJavascript", $NewValue);
440 public function JavascriptMinimizationEnabled($NewValue =
DB_NOVALUE)
442 return $this->UpdateSetting(
"JavascriptMinimizationEnabled", $NewValue);
458 public function RecordContextInCaseOfCrash(
459 $BacktraceOptions = 0, $BacktraceLimit = 0)
461 if (version_compare(PHP_VERSION,
"5.4.0",
">="))
463 $this->SavedContext = debug_backtrace(
464 $BacktraceOptions, $BacktraceLimit);
468 $this->SavedContext = debug_backtrace($BacktraceOptions);
470 array_shift($this->SavedContext);
477 public function LoadPage($PageName)
479 # perform any clean URL rewriting 480 $PageName = $this->RewriteCleanUrls($PageName);
482 # sanitize incoming page name and save local copy 483 $PageName = preg_replace(
"/[^a-zA-Z0-9_.-]/",
"", $PageName);
484 $this->PageName = $PageName;
486 # if page caching is turned on 487 if ($this->PageCacheEnabled())
489 # if we have a cached page 490 $CachedPage = $this->CheckForCachedPage($PageName);
491 if ($CachedPage !== NULL)
493 # set header to indicate cache hit was found 494 header(
"X-ScoutAF-Cache: HIT");
496 # display cached page and exit 502 # set header to indicate no cache hit was found 503 header(
"X-ScoutAF-Cache: MISS");
507 # buffer any output from includes or PHP file 510 # include any files needed to set up execution environment 511 foreach ($this->EnvIncludes as $IncludeFile)
513 include($IncludeFile);
517 $this->SignalEvent(
"EVENT_PAGE_LOAD", array(
"PageName" => $PageName));
519 # signal PHP file load 520 $SignalResult = $this->SignalEvent(
"EVENT_PHP_FILE_LOAD", array(
521 "PageName" => $PageName));
523 # if signal handler returned new page name value 524 $NewPageName = $PageName;
525 if (($SignalResult[
"PageName"] != $PageName)
526 && strlen($SignalResult[
"PageName"]))
528 # if new page name value is page file 529 if (file_exists($SignalResult[
"PageName"]))
531 # use new value for PHP file name 532 $PageFile = $SignalResult[
"PageName"];
536 # use new value for page name 537 $NewPageName = $SignalResult[
"PageName"];
540 # update local copy of page name 541 $this->PageName = $NewPageName;
544 # if we do not already have a PHP file 545 if (!isset($PageFile))
547 # look for PHP file for page 548 $OurPageFile =
"pages/".$NewPageName.
".php";
549 $LocalPageFile =
"local/pages/".$NewPageName.
".php";
550 $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
551 : (file_exists($OurPageFile) ? $OurPageFile
552 :
"pages/".$this->DefaultPage.
".php");
558 # save buffered output to be displayed later after HTML file loads 559 $PageOutput = ob_get_contents();
562 # signal PHP file load is complete 564 $Context[
"Variables"] = get_defined_vars();
565 $this->SignalEvent(
"EVENT_PHP_FILE_LOAD_COMPLETE",
566 array(
"PageName" => $PageName,
"Context" => $Context));
567 $PageCompleteOutput = ob_get_contents();
570 # set up for possible TSR (Terminate and Stay Resident :)) 571 $ShouldTSR = $this->PrepForTSR();
573 # if PHP file indicated we should autorefresh to somewhere else 574 if (($this->JumpToPage) && ($this->JumpToPageDelay == 0))
576 if (!strlen(trim($PageOutput)))
578 # if client supports HTTP/1.1, use a 303 as it is most accurate 579 if ($_SERVER[
"SERVER_PROTOCOL"] ==
"HTTP/1.1")
581 header(
"HTTP/1.1 303 See Other");
582 header(
"Location: ".$this->JumpToPage);
586 # if the request was an HTTP/1.0 GET or HEAD, then 587 # use a 302 response code. 589 # NB: both RFC 2616 (HTTP/1.1) and RFC1945 (HTTP/1.0) 590 # explicitly prohibit automatic redirection via a 302 591 # if the request was not GET or HEAD. 592 if ($_SERVER[
"SERVER_PROTOCOL"] ==
"HTTP/1.0" &&
593 ($_SERVER[
"REQUEST_METHOD"] ==
"GET" ||
594 $_SERVER[
"REQUEST_METHOD"] ==
"HEAD") )
596 header(
"HTTP/1.0 302 Found");
597 header(
"Location: ".$this->JumpToPage);
600 # otherwise, fall back to a meta refresh 603 print
'<html><head><meta http-equiv="refresh" ' 604 .
'content="0; URL='.$this->JumpToPage.
'">' 605 .
'</head><body></body></html>';
610 # else if HTML loading is not suppressed 611 elseif (!$this->SuppressHTML)
613 # set content-type to get rid of diacritic errors 614 header(
"Content-Type: text/html; charset=" 615 .$this->HtmlCharset, TRUE);
617 # load common HTML file (defines common functions) if available 618 $CommonHtmlFile = $this->FindFile($this->IncludeDirList,
619 "Common", array(
"tpl",
"html"));
620 if ($CommonHtmlFile) { include($CommonHtmlFile); }
623 $this->LoadUIFunctions();
625 # begin buffering content 628 # signal HTML file load 629 $SignalResult = $this->SignalEvent(
"EVENT_HTML_FILE_LOAD", array(
630 "PageName" => $PageName));
632 # if signal handler returned new page name value 633 $NewPageName = $PageName;
634 $PageContentFile = NULL;
635 if (($SignalResult[
"PageName"] != $PageName)
636 && strlen($SignalResult[
"PageName"]))
638 # if new page name value is HTML file 639 if (file_exists($SignalResult[
"PageName"]))
641 # use new value for HTML file name 642 $PageContentFile = $SignalResult[
"PageName"];
646 # use new value for page name 647 $NewPageName = $SignalResult[
"PageName"];
651 # load page content HTML file if available 652 if ($PageContentFile === NULL)
654 $PageContentFile = $this->FindFile(
655 $this->InterfaceDirList, $NewPageName,
656 array(
"tpl",
"html"));
658 if ($PageContentFile)
660 include($PageContentFile);
664 print
"<h2>ERROR: No HTML/TPL template found" 665 .
" for this page (".$NewPageName.
").</h2>";
668 # signal HTML file load complete 669 $SignalResult = $this->SignalEvent(
"EVENT_HTML_FILE_LOAD_COMPLETE");
671 # stop buffering and save output 672 $PageContentOutput = ob_get_contents();
675 # load page start HTML file if available 677 $PageStartFile = $this->FindFile($this->IncludeDirList,
"Start",
678 array(
"tpl",
"html"), array(
"StdPage",
"StandardPage"));
679 if ($PageStartFile) { include($PageStartFile); }
680 $PageStartOutput = ob_get_contents();
683 # if page auto-refresh requested 684 if ($this->JumpToPage)
686 $RefreshLine =
'<meta http-equiv="refresh" content="' 687 .$this->JumpToPageDelay.
'; url='.$this->JumpToPage.
'">';
688 $PageStartOutput = str_replace(
"<head>",
"<head>\n".$RefreshLine,
692 # load page end HTML file if available 694 $PageEndFile = $this->FindFile($this->IncludeDirList,
"End",
695 array(
"tpl",
"html"), array(
"StdPage",
"StandardPage"));
696 if ($PageEndFile) { include($PageEndFile); }
697 $PageEndOutput = ob_get_contents();
700 # get list of any required files not loaded 701 $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
703 # if a browser detection function has been made available 704 if (is_callable($this->BrowserDetectFunc))
706 # call function to get browser list 707 $Browsers = call_user_func($this->BrowserDetectFunc);
709 # for each required file 710 $NewRequiredFiles = array();
711 foreach ($RequiredFiles as $File)
713 # if file name includes browser keyword 714 if (preg_match(
"/%BROWSER%/", $File))
717 foreach ($Browsers as $Browser)
719 # substitute in browser name and add to new file list 720 $NewRequiredFiles[] = preg_replace(
721 "/%BROWSER%/", $Browser, $File);
726 # add to new file list 727 $NewRequiredFiles[] = $File;
730 $RequiredFiles = $NewRequiredFiles;
734 # filter out any files with browser keyword in their name 735 $NewRequiredFiles = array();
736 foreach ($RequiredFiles as $File)
738 if (!preg_match(
"/%BROWSER%/", $File))
740 $NewRequiredFiles[] = $File;
743 $RequiredFiles = $NewRequiredFiles;
746 # for each required file 747 foreach ($RequiredFiles as $File)
749 # locate specific file to use 750 $FilePath = $this->GUIFile($File);
755 # generate tag for file 756 $Tag = $this->GetUIFileLoadingTag($FilePath);
758 # add file to HTML output based on file type 759 $FileType = $this->GetFileType($FilePath);
763 $PageStartOutput = preg_replace(
764 "#</head>#i", $Tag.
"\n</head>", $PageStartOutput, 1);
767 case self::FT_JAVASCRIPT:
768 $PageEndOutput = preg_replace(
769 "#</body>#i", $Tag.
"\n</body>", $PageEndOutput, 1);
776 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
778 # perform any regular expression replacements in output 779 $NewFullPageOutput = preg_replace($this->OutputModificationPatterns,
780 $this->OutputModificationReplacements, $FullPageOutput);
782 # check to make sure replacements didn't fail 783 $FullPageOutput = $this->CheckOutputModification(
784 $FullPageOutput, $NewFullPageOutput,
785 "regular expression replacements");
787 # for each registered output modification callback 788 foreach ($this->OutputModificationCallbacks as $Info)
790 # set up data for callback 791 $this->OutputModificationCallbackInfo = $Info;
793 # perform output modification 794 $NewFullPageOutput = preg_replace_callback($Info[
"SearchPattern"],
795 array($this,
"OutputModificationCallbackShell"),
798 # check to make sure modification didn't fail 799 $ErrorInfo =
"callback info: ".print_r($Info, TRUE);
800 $FullPageOutput = $this->CheckOutputModification(
801 $FullPageOutput, $NewFullPageOutput, $ErrorInfo);
804 # provide the opportunity to modify full page output 805 $SignalResult = $this->SignalEvent(
"EVENT_PAGE_OUTPUT_FILTER", array(
806 "PageOutput" => $FullPageOutput));
807 if (isset($SignalResult[
"PageOutput"])
808 && strlen(trim($SignalResult[
"PageOutput"])))
810 $FullPageOutput = $SignalResult[
"PageOutput"];
813 # if relative paths may not work because we were invoked via clean URL 814 if ($this->CleanUrlRewritePerformed || self::WasUrlRewritten())
816 # if using the <base> tag is okay 817 $BaseUrl = $this->BaseUrl();
818 if ($this->UseBaseTag)
820 # add <base> tag to header 821 $PageStartOutput = str_replace(
"<head>",
822 "<head><base href=\"".$BaseUrl.
"\" />",
825 # re-assemble full page with new header 826 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
828 # the absolute URL to the current page 829 $FullUrl = $BaseUrl . $this->GetPageLocation();
831 # make HREF attribute values with just a fragment ID 832 # absolute since they don't work with the <base> tag because 833 # they are relative to the current page/URL, not the site 835 $NewFullPageOutput = preg_replace(
836 array(
"%href=\"(#[^:\" ]+)\"%i",
"%href='(#[^:' ]+)'%i"),
837 array(
"href=\"".$FullUrl.
"$1\"",
"href='".$FullUrl.
"$1'"),
840 # check to make sure HREF cleanup didn't fail 841 $FullPageOutput = $this->CheckOutputModification(
842 $FullPageOutput, $NewFullPageOutput,
847 # try to fix any relative paths throughout code 848 $RelativePathPatterns = array(
849 "%src=\"/?([^?*:;{}\\\\\" ]+)\.(js|css|gif|png|jpg)\"%i",
850 "%src='/?([^?*:;{}\\\\' ]+)\.(js|css|gif|png|jpg)'%i",
851 # don
't rewrite HREF attributes that are just 852 # fragment IDs because they are relative to the 853 # current page/URL, not the site root 854 "%href=\"/?([^#][^:\" ]*)\"%i", 855 "%href='/?([^#][^:
' ]*)'%i
", 856 "%action=\
"/?([^#][^:\" ]*)\"%i",
857 "%action='/?([^#][^:' ]*)'%i",
858 "%@import\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
859 "%@import\s+url\('/?([^:\" ]+)'\s*\)%i",
860 "%src:\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
861 "%src:\s+url\('/?([^:\" ]+)'\s*\)%i",
862 "%@import\s+\"/?([^:\" ]+)\"\s*%i",
863 "%@import\s+'/?([^:\" ]+)'\s*%i",
865 $RelativePathReplacements = array(
866 "src=\"".$BaseUrl.
"$1.$2\"",
867 "src=\"".$BaseUrl.
"$1.$2\"",
868 "href=\"".$BaseUrl.
"$1\"",
869 "href=\"".$BaseUrl.
"$1\"",
870 "action=\"".$BaseUrl.
"$1\"",
871 "action=\"".$BaseUrl.
"$1\"",
872 "@import url(\"".$BaseUrl.
"$1\")",
873 "@import url('".$BaseUrl.
"$1')",
874 "src: url(\"".$BaseUrl.
"$1\")",
875 "src: url('".$BaseUrl.
"$1')",
876 "@import \"".$BaseUrl.
"$1\"",
877 "@import '".$BaseUrl.
"$1'",
879 $NewFullPageOutput = preg_replace($RelativePathPatterns,
880 $RelativePathReplacements, $FullPageOutput);
882 # check to make sure relative path fixes didn't fail 883 $FullPageOutput = $this->CheckOutputModification(
884 $FullPageOutput, $NewFullPageOutput,
885 "relative path fixes");
889 # handle any necessary alternate domain rewriting 890 $FullPageOutput = $this->RewriteAlternateDomainUrls($FullPageOutput);
892 # update page cache for this page 893 $this->UpdatePageCache($PageName, $FullPageOutput);
895 # write out full page 896 print $FullPageOutput;
899 # run any post-processing routines 900 foreach ($this->PostProcessingFuncs as $Func)
902 call_user_func_array($Func[
"FunctionName"], $Func[
"Arguments"]);
905 # write out any output buffered from page code execution 906 if (strlen($PageOutput))
908 if (!$this->SuppressHTML)
910 ?><table width=
"100%" cellpadding=
"5" 911 style=
"border: 2px solid #666666; background: #CCCCCC; 912 font-family: Courier New, Courier, monospace; 913 margin-top: 10px;"><tr><td><?
PHP 915 if ($this->JumpToPage)
917 ?><div style=
"color: #666666;"><span style=
"font-size: 150%;">
918 <b>Page Jump Aborted</b></span>
919 (because of error or other unexpected output)<br />
921 <i><?
PHP print($this->JumpToPage); ?></i></div><?
PHP 924 if (!$this->SuppressHTML)
926 ?></td></tr></table><?
PHP 930 # write out any output buffered from the page code execution complete signal 931 if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
933 print $PageCompleteOutput;
936 # log slow page loads 937 if ($this->LogSlowPageLoads()
938 && !$this->DoNotLogSlowPageLoad
939 && ($this->GetElapsedExecutionTime()
940 >= ($this->SlowPageLoadThreshold())))
942 $RemoteHost = gethostbyaddr($_SERVER[
"REMOTE_ADDR"]);
943 if ($RemoteHost === FALSE)
945 $RemoteHost = $_SERVER[
"REMOTE_ADDR"];
947 elseif ($RemoteHost != $_SERVER[
"REMOTE_ADDR"])
949 $RemoteHost .=
" (".$_SERVER[
"REMOTE_ADDR"].
")";
951 $SlowPageLoadMsg =
"Slow page load (" 952 .intval($this->GetElapsedExecutionTime()).
"s) for " 953 .$this->FullUrl().
" from ".$RemoteHost;
954 $this->LogMessage(self::LOGLVL_INFO, $SlowPageLoadMsg);
957 # execute callbacks that should not have their output buffered 958 foreach ($this->UnbufferedCallbacks as $Callback)
960 call_user_func_array($Callback[0], $Callback[1]);
963 # log high memory usage 964 if (function_exists(
"memory_get_peak_usage"))
966 $MemoryThreshold = ($this->HighMemoryUsageThreshold()
967 * $this->GetPhpMemoryLimit()) / 100;
968 if ($this->LogHighMemoryUsage()
969 && (memory_get_peak_usage() >= $MemoryThreshold))
971 $HighMemUsageMsg =
"High peak memory usage (" 972 .intval(memory_get_peak_usage()).
") for " 973 .$this->FullUrl().
" from " 974 .$_SERVER[
"REMOTE_ADDR"];
975 $this->LogMessage(self::LOGLVL_INFO, $HighMemUsageMsg);
979 # terminate and stay resident (TSR!) if indicated and HTML has been output 980 # (only TSR if HTML has been output because otherwise browsers will misbehave) 981 if ($ShouldTSR) { $this->LaunchTSR(); }
989 public function GetPageName()
991 return $this->PageName;
999 public function GetPageLocation()
1001 # retrieve current URL 1002 $Url = $this->GetScriptUrl();
1004 # remove the base path if present 1005 $BasePath = $this->Settings[
"BasePath"];
1006 if (stripos($Url, $BasePath) === 0)
1008 $Url = substr($Url, strlen($BasePath));
1011 # if we're being accessed via an alternate domain, 1012 # add the appropriate prefix in 1013 if ($this->HtaccessSupport() &&
1014 self::$RootUrlOverride !== NULL)
1016 $VHost = $_SERVER[
"SERVER_NAME"];
1017 if (isset($this->AlternateDomainPrefixes[$VHost]))
1019 $ThisPrefix = $this->AlternateDomainPrefixes[$VHost];
1020 $Url = $ThisPrefix.
"/".$Url;
1032 public function GetPageUrl()
1034 return self::BaseUrl() . $this->GetPageLocation();
1048 public function SetJumpToPage($Page, $Delay = 0, $IsLiteral = FALSE)
1052 && (strpos($Page,
"?") === FALSE)
1053 && ((strpos($Page,
"=") !== FALSE)
1054 || ((stripos($Page,
".php") === FALSE)
1055 && (stripos($Page,
".htm") === FALSE)
1056 && (strpos($Page,
"/") === FALSE)))
1057 && (stripos($Page,
"http://") !== 0)
1058 && (stripos($Page,
"https://") !== 0))
1060 $this->JumpToPage = self::BaseUrl() .
"index.php?P=".$Page;
1064 $this->JumpToPage = $Page;
1066 $this->JumpToPageDelay = $Delay;
1073 public function JumpToPageIsSet()
1075 return ($this->JumpToPage === NULL) ? FALSE : TRUE;
1087 public function HtmlCharset($NewSetting = NULL)
1089 if ($NewSetting !== NULL) { $this->HtmlCharset = $NewSetting; }
1090 return $this->HtmlCharset;
1102 public function DoNotMinimizeFile($File)
1104 if (!is_array($File)) { $File = array($File); }
1105 $this->DoNotMinimizeList = array_merge($this->DoNotMinimizeList, $File);
1118 public function UseBaseTag($NewValue = NULL)
1120 if ($NewValue !== NULL) { $this->UseBaseTag = $NewValue ? TRUE : FALSE; }
1121 return $this->UseBaseTag;
1130 public function SuppressHTMLOutput($NewSetting = TRUE)
1132 $this->SuppressHTML = $NewSetting;
1140 public static function DefaultUserInterface($UIName = NULL)
1142 if ($UIName !== NULL)
1144 self::$DefaultUI = $UIName;
1146 return self::$DefaultUI;
1155 public static function ActiveUserInterface($UIName = NULL)
1157 if ($UIName !== NULL)
1159 self::$ActiveUI = preg_replace(
"/^SPTUI--/",
"", $UIName);
1161 return self::$ActiveUI;
1174 public function GetUserInterfaces($FilterExp = NULL)
1178 if (!isset($Interfaces[$FilterExp]))
1180 # retrieve paths to user interface directories 1181 $Paths = $this->GetUserInterfacePaths($FilterExp);
1183 # start out with an empty list 1184 $Interfaces[$FilterExp] = array();
1186 # for each possible UI directory 1187 foreach ($Paths as $CanonicalName => $Path)
1189 # if name file available 1190 $LabelFile = $Path.
"/NAME";
1191 if (is_readable($LabelFile))
1194 $Label = file_get_contents($LabelFile);
1196 # if the UI name looks reasonable 1197 if (strlen(trim($Label)))
1200 $Interfaces[$FilterExp][$CanonicalName] = $Label;
1204 # if we do not have a name yet 1205 if (!isset($Interfaces[$FilterExp][$CanonicalName]))
1207 # use base directory for name 1208 $Interfaces[$FilterExp][$CanonicalName] = basename($Path);
1213 # return list to caller 1214 return $Interfaces[$FilterExp];
1225 public function GetUserInterfacePaths($FilterExp = NULL)
1227 static $InterfacePaths;
1229 if (!isset($InterfacePaths[$FilterExp]))
1231 # extract possible UI directories from interface directory list 1232 $InterfaceDirs = array();
1233 foreach ($this->InterfaceDirList as $Dir)
1236 if (preg_match(
"#([a-zA-Z0-9/]*interface)/[a-zA-Z0-9%/]*#",
1240 if (!in_array($Dir, $InterfaceDirs))
1242 $InterfaceDirs[] = $Dir;
1247 # reverse order of interface directories so that the directory 1248 # returned is the base directory for the interface 1249 $InterfaceDirs = array_reverse($InterfaceDirs);
1251 # start out with an empty list 1252 $InterfacePaths[$FilterExp] = array();
1253 $InterfacesFound = array();
1255 # for each possible UI directory 1256 foreach ($InterfaceDirs as $InterfaceDir)
1258 $Dir = dir($InterfaceDir);
1260 # for each file in current directory 1261 while (($DirEntry = $Dir->read()) !== FALSE)
1263 $InterfacePath = $InterfaceDir.
"/".$DirEntry;
1265 # skip anything we have already found 1266 # or that doesn't have a name in the required format 1267 # or that isn't a directory 1268 # or that doesn't match the filter regex (if supplied) 1269 if (in_array($DirEntry, $InterfacesFound)
1270 || !preg_match(
'/^[a-zA-Z0-9]+$/', $DirEntry)
1271 || !is_dir($InterfacePath)
1272 || (($FilterExp !== NULL)
1273 && !preg_match($FilterExp, $InterfacePath)))
1278 # add interface to list 1279 $InterfacePaths[$FilterExp][$DirEntry] = $InterfacePath;
1280 $InterfacesFound[] = $DirEntry;
1287 # return list to caller 1288 return $InterfacePaths[$FilterExp];
1315 public function AddPostProcessingCall($FunctionName,
1316 &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
1317 &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
1318 &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
1320 $FuncIndex = count($this->PostProcessingFuncs);
1321 $this->PostProcessingFuncs[$FuncIndex][
"FunctionName"] = $FunctionName;
1322 $this->PostProcessingFuncs[$FuncIndex][
"Arguments"] = array();
1324 while (isset(${
"Arg".$Index}) && (${
"Arg".$Index} !== self::NOVALUE))
1326 $this->PostProcessingFuncs[$FuncIndex][
"Arguments"][$Index]
1337 public function AddEnvInclude($FileName)
1339 $this->EnvIncludes[] = $FileName;
1348 public function GUIFile($FileName)
1350 # determine which location to search based on file suffix 1351 $FileType = $this->GetFileType($FileName);
1352 $DirList = ($FileType == self::FT_IMAGE)
1353 ? $this->ImageDirList : $this->IncludeDirList;
1355 # if directed to use minimized JavaScript file 1356 if (($FileType == self::FT_JAVASCRIPT) && $this->UseMinimizedJavascript())
1358 # look for minimized version of file 1359 $MinimizedFileName = substr_replace($FileName,
".min", -3, 0);
1360 $FoundFileName = $this->FindFile($DirList, $MinimizedFileName);
1362 # if minimized file was not found 1363 if (is_null($FoundFileName))
1365 # look for unminimized file 1366 $FoundFileName = $this->FindFile($DirList, $FileName);
1368 # if unminimized file found 1369 if (!is_null($FoundFileName))
1371 # if minimization enabled and supported 1372 if ($this->JavascriptMinimizationEnabled()
1373 && self::JsMinRewriteSupport())
1375 # attempt to create minimized file 1376 $MinFileName = $this->MinimizeJavascriptFile(
1379 # if minimization succeeded 1380 if ($MinFileName !== NULL)
1382 # use minimized version 1383 $FoundFileName = $MinFileName;
1385 # save file modification time if needed for fingerprinting 1386 if ($this->UrlFingerprintingEnabled())
1388 $FileMTime = filemtime($FoundFileName);
1391 # strip off the cache location, allowing .htaccess 1392 # to handle that for us 1393 $FoundFileName = str_replace(
1394 self::$JSMinCacheDir.
"/",
"", $FoundFileName);
1400 # else if directed to use SCSS files 1401 elseif (($FileType == self::FT_CSS) && $this->ScssSupportEnabled())
1403 # look for SCSS version of file 1404 $SourceFileName = preg_replace(
"/.css$/",
".scss", $FileName);
1405 $FoundSourceFileName = $this->FindFile($DirList, $SourceFileName);
1407 # if SCSS file not found 1408 if ($FoundSourceFileName === NULL)
1411 $FoundFileName = $this->FindFile($DirList, $FileName);
1415 # compile SCSS file (if updated) and return resulting CSS file 1416 $FoundFileName = $this->CompileScssFile($FoundSourceFileName);
1418 # save file modification time if needed for fingerprinting 1419 if ($this->UrlFingerprintingEnabled())
1421 $FileMTime = filemtime($FoundFileName);
1424 # strip off the cache location, allowing .htaccess to handle that for us 1425 if (self::ScssRewriteSupport())
1427 $FoundFileName = str_replace(
1428 self::$ScssCacheDir.
"/",
"", $FoundFileName);
1432 # otherwise just search for the file 1435 $FoundFileName = $this->FindFile($DirList, $FileName);
1438 # add non-image files to list of found files (used for required files loading) 1439 if ($FileType != self::FT_IMAGE)
1440 { $this->FoundUIFiles[] = basename($FoundFileName); }
1442 # if UI file fingerprinting is enabled and supported 1443 if ($this->UrlFingerprintingEnabled()
1444 && self::UrlFingerprintingRewriteSupport()
1445 && (isset($FileMTime) || file_exists($FoundFileName)))
1447 # if file does not appear to be a server-side inclusion 1448 if (!preg_match(
'/\.(html|php)$/i', $FoundFileName))
1450 # for each URL fingerprinting blacklist entry 1451 $OnBlacklist = FALSE;
1452 foreach ($this->UrlFingerprintBlacklist as $BlacklistEntry)
1454 # if entry looks like a regular expression pattern 1455 if ($BlacklistEntry[0] == substr($BlacklistEntry, -1))
1457 # check file name against regular expression 1458 if (preg_match($BlacklistEntry, $FoundFileName))
1460 $OnBlacklist = TRUE;
1466 # check file name directly against entry 1467 if (basename($FoundFileName) == $BlacklistEntry)
1469 $OnBlacklist = TRUE;
1475 # if file was not on blacklist 1478 # get file modification time if not already retrieved 1479 if (!isset($FileMTime))
1481 $FileMTime = filemtime($FoundFileName);
1484 # add timestamp fingerprint to file name 1485 $Fingerprint = sprintf(
"%06X",
1486 ($FileMTime % 0xFFFFFF));
1487 $FoundFileName = preg_replace(
"/^(.+)\.([a-z]+)$/",
1488 "$1.".$Fingerprint.
".$2",
1494 # return file name to caller 1495 return $FoundFileName;
1506 public function PUIFile($FileName)
1508 $FullFileName = $this->GUIFile($FileName);
1509 if ($FullFileName) { print($FullFileName); }
1526 public function IncludeUIFile($FileNames, $AdditionalAttributes = NULL)
1528 # convert file name to array if necessary 1529 if (!is_array($FileNames)) { $FileNames = array($FileNames); }
1531 # pad additional attributes if supplied 1532 $AddAttribs = $AdditionalAttributes ?
" ".$AdditionalAttributes :
"";
1535 foreach ($FileNames as $BaseFileName)
1537 # retrieve full file name 1538 $FileName = $this->GUIFile($BaseFileName);
1543 # print appropriate tag 1544 print $this->GetUIFileLoadingTag(
1545 $FileName, $AdditionalAttributes);
1548 # if we are not already loading an override file 1549 if (!preg_match(
"/-Override.(css|scss|js)$/", $BaseFileName))
1551 # attempt to load override file if available 1552 $FileType = $this->GetFileType($BaseFileName);
1556 $OverrideFileName = preg_replace(
1557 "/\.(css|scss)$/",
"-Override.$1",
1559 $this->IncludeUIFile($OverrideFileName,
1560 $AdditionalAttributes);
1563 case self::FT_JAVASCRIPT:
1564 $OverrideFileName = preg_replace(
1565 "/\.js$/",
"-Override.js",
1567 $this->IncludeUIFile($OverrideFileName,
1568 $AdditionalAttributes);
1581 public function DoNotUrlFingerprint($Pattern)
1583 $this->UrlFingerprintBlacklist[] = $Pattern;
1593 public function RequireUIFile($FileName)
1595 $this->AdditionalRequiredUIFiles[] = $FileName;
1603 public static function GetFileType($FileName)
1605 static $FileTypeCache;
1606 if (isset($FileTypeCache[$FileName]))
1608 return $FileTypeCache[$FileName];
1611 $FileSuffix = strtolower(substr($FileName, -3));
1612 if ($FileSuffix ==
"css")
1614 $FileTypeCache[$FileName] = self::FT_CSS;
1616 elseif ($FileSuffix ==
".js")
1618 $FileTypeCache[$FileName] = self::FT_JAVASCRIPT;
1620 elseif (($FileSuffix ==
"gif")
1621 || ($FileSuffix ==
"jpg")
1622 || ($FileSuffix ==
"png"))
1624 $FileTypeCache[$FileName] = self::FT_IMAGE;
1628 $FileTypeCache[$FileName] = self::FT_OTHER;
1631 return $FileTypeCache[$FileName];
1640 const FT_JAVASCRIPT = 3;
1650 public function LoadFunction($Callback)
1652 # if specified function is not currently available 1653 if (!is_callable($Callback))
1655 # if function info looks legal 1656 if (is_string($Callback) && strlen($Callback))
1658 # start with function directory list 1659 $Locations = $this->FunctionDirList;
1661 # add object directories to list 1662 $Locations = array_merge(
1663 $Locations, array_keys(self::$ObjectDirectories));
1665 # look for function file 1666 $FunctionFileName = $this->FindFile($Locations,
"F-".$Callback,
1667 array(
"php",
"html"));
1669 # if function file was found 1670 if ($FunctionFileName)
1672 # load function file 1673 include_once($FunctionFileName);
1677 # log error indicating function load failed 1678 $this->LogError(self::LOGLVL_ERROR,
"Unable to load function" 1679 .
" for callback \"".$Callback.
"\".");
1684 # log error indicating specified function info was bad 1685 $this->LogError(self::LOGLVL_ERROR,
"Unloadable callback value" 1687 .
" passed to AF::LoadFunction() by " 1688 .StdLib::GetMyCaller().
".");
1692 # report to caller whether function load succeeded 1693 return is_callable($Callback);
1700 public function GetElapsedExecutionTime()
1702 return microtime(TRUE) - $this->ExecutionStartTime;
1709 public function GetSecondsBeforeTimeout()
1711 return ini_get(
"max_execution_time") - $this->GetElapsedExecutionTime();
1717 # ---- Page Caching ------------------------------------------------------ 1727 public function PageCacheEnabled($NewValue =
DB_NOVALUE)
1729 return $this->UpdateSetting(
"PageCacheEnabled", $NewValue);
1738 public function PageCacheExpirationPeriod($NewValue =
DB_NOVALUE)
1740 return $this->UpdateSetting(
"PageCacheExpirationPeriod", $NewValue);
1747 public function DoNotCacheCurrentPage()
1749 $this->CacheCurrentPage = FALSE;
1758 public function AddPageCacheTag($Tag, $Pages = NULL)
1761 $Tag = strtolower($Tag);
1763 # if pages were supplied 1764 if ($Pages !== NULL)
1766 # add pages to list for this tag 1767 if (isset($this->PageCacheTags[$Tag]))
1769 $this->PageCacheTags[$Tag] = array_merge(
1770 $this->PageCacheTags[$Tag], $Pages);
1774 $this->PageCacheTags[$Tag] = $Pages;
1779 # add current page to list for this tag 1780 $this->PageCacheTags[$Tag][] =
"CURRENT";
1789 public function ClearPageCacheForTag($Tag)
1792 $TagId = $this->GetPageCacheTagId($Tag);
1794 # delete pages and tag/page connections for specified tag 1795 $this->DB->Query(
"DELETE CP, CPTI" 1796 .
" FROM AF_CachedPages CP, AF_CachedPageTagInts CPTI" 1797 .
" WHERE CPTI.TagId = ".intval($TagId)
1798 .
" AND CP.CacheId = CPTI.CacheId");
1804 public function ClearPageCache()
1806 # clear all page cache tables 1807 $this->DB->Query(
"TRUNCATE TABLE AF_CachedPages");
1808 $this->DB->Query(
"TRUNCATE TABLE AF_CachedPageTags");
1809 $this->DB->Query(
"TRUNCATE TABLE AF_CachedPageTagInts");
1818 public function GetPageCacheInfo()
1820 $Length = $this->DB->Query(
"SELECT COUNT(*) AS CacheLen" 1821 .
" FROM AF_CachedPages",
"CacheLen");
1822 $Oldest = $this->DB->Query(
"SELECT CachedAt FROM AF_CachedPages" 1823 .
" ORDER BY CachedAt ASC LIMIT 1",
"CachedAt");
1825 "NumberOfEntries" => $Length,
1826 "OldestTimestamp" => strtotime($Oldest),
1833 # ---- Logging ----------------------------------------------------------- 1850 public function LogSlowPageLoads(
1853 return $this->UpdateSetting(
1854 "LogSlowPageLoads", $NewValue, $Persistent);
1867 public function SlowPageLoadThreshold(
1870 return $this->UpdateSetting(
1871 "SlowPageLoadThreshold", $NewValue, $Persistent);
1887 public function LogHighMemoryUsage(
1890 return $this->UpdateSetting(
1891 "LogHighMemoryUsage", $NewValue, $Persistent);
1905 public function HighMemoryUsageThreshold(
1908 return $this->UpdateSetting(
1909 "HighMemoryUsageThreshold", $NewValue, $Persistent);
1925 public function LogError($Level, $Msg)
1927 # if error level is at or below current logging level 1928 if ($this->Settings[
"LoggingLevel"] >= $Level)
1930 # attempt to log error message 1931 $Result = $this->LogMessage($Level, $Msg);
1933 # if logging attempt failed and level indicated significant error 1934 if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
1936 # throw exception about inability to log error 1937 static $AlreadyThrewException = FALSE;
1938 if (!$AlreadyThrewException)
1940 $AlreadyThrewException = TRUE;
1941 throw new Exception(
"Unable to log error (".$Level.
": ".$Msg
1942 .
") to ".$this->LogFileName);
1946 # report to caller whether message was logged 1951 # report to caller that message was not logged 1967 public function LogMessage($Level, $Msg)
1969 # if message level is at or below current logging level 1970 if ($this->Settings[
"LoggingLevel"] >= $Level)
1972 # attempt to open log file 1973 $FHndl = @fopen($this->LogFileName,
"a");
1975 # if log file could not be open 1976 if ($FHndl === FALSE)
1978 # report to caller that message was not logged 1984 $ErrorAbbrevs = array(
1985 self::LOGLVL_FATAL =>
"FTL",
1986 self::LOGLVL_ERROR =>
"ERR",
1987 self::LOGLVL_WARNING =>
"WRN",
1988 self::LOGLVL_INFO =>
"INF",
1989 self::LOGLVL_DEBUG =>
"DBG",
1990 self::LOGLVL_TRACE =>
"TRC",
1992 $Msg = str_replace(array(
"\n",
"\t",
"\r"),
" ", $Msg);
1993 $Msg = substr(trim($Msg), 0, self::LOGFILE_MAX_LINE_LENGTH);
1994 $LogEntry = date(
"Y-m-d H:i:s")
1995 .
" ".($this->RunningInBackground ?
"B" :
"F")
1996 .
" ".$ErrorAbbrevs[$Level]
1999 # write entry to log 2000 $Success = fwrite($FHndl, $LogEntry.
"\n");
2005 # report to caller whether message was logged 2006 return ($Success === FALSE) ? FALSE : TRUE;
2011 # report to caller that message was not logged 2037 public function LoggingLevel($NewValue =
DB_NOVALUE)
2039 # constrain new level (if supplied) to within legal bounds 2042 $NewValue = max(min($NewValue, 6), 1);
2045 # set new logging level (if supplied) and return current level to caller 2046 return $this->UpdateSetting(
"LoggingLevel", $NewValue);
2055 public function LogFile($NewValue = NULL)
2057 if ($NewValue !== NULL) { $this->LogFileName = $NewValue; }
2058 return $this->LogFileName;
2070 public function GetLogEntries($Limit = 0)
2072 # return no entries if there isn't a log file or we can't read it 2073 if (!is_readable($this->LogFile()))
2078 # if max number of entries specified 2081 # load lines from file 2082 $FHandle = fopen($this->LogFile(),
"r");
2083 $FileSize = filesize($this->LogFile());
2084 $SeekPosition = max(0,
2085 ($FileSize - (self::LOGFILE_MAX_LINE_LENGTH * ($Limit + 1))));
2086 fseek($FHandle, $SeekPosition);
2087 $Block = fread($FHandle, ($FileSize - $SeekPosition));
2089 $Lines = explode(PHP_EOL, $Block);
2090 array_shift($Lines);
2092 # prune array back to requested number of entries 2093 $Lines = array_slice($Lines, (0 - $Limit));
2097 # load all lines from log file 2098 $Lines = file($this->LogFile(), FILE_IGNORE_NEW_LINES);
2099 if ($Lines === FALSE)
2105 # reverse line order 2106 $Lines = array_reverse($Lines);
2108 # for each log file line 2110 foreach ($Lines as $Line)
2112 # attempt to parse line into component parts 2113 $Pieces = explode(
" ", $Line, 5);
2114 $Date = isset($Pieces[0]) ? $Pieces[0] :
"";
2115 $Time = isset($Pieces[1]) ? $Pieces[1] :
"";
2116 $Back = isset($Pieces[2]) ? $Pieces[2] :
"";
2117 $Level = isset($Pieces[3]) ? $Pieces[3] :
"";
2118 $Msg = isset($Pieces[4]) ? $Pieces[4] :
"";
2120 # skip line if it looks invalid 2121 $ErrorAbbrevs = array(
2122 "FTL" => self::LOGLVL_FATAL,
2123 "ERR" => self::LOGLVL_ERROR,
2124 "WRN" => self::LOGLVL_WARNING,
2125 "INF" => self::LOGLVL_INFO,
2126 "DBG" => self::LOGLVL_DEBUG,
2127 "TRC" => self::LOGLVL_TRACE,
2129 if ((($Back !=
"F") && ($Back !=
"B"))
2130 || !array_key_exists($Level, $ErrorAbbrevs)
2136 # convert parts into appropriate values and add to entries 2138 "Time" => strtotime($Date.
" ".$Time),
2139 "Background" => ($Back ==
"B") ? TRUE : FALSE,
2140 "Level" => $ErrorAbbrevs[$Level],
2145 # return entries to caller 2153 const LOGLVL_TRACE = 6;
2158 const LOGLVL_DEBUG = 5;
2164 const LOGLVL_INFO = 4;
2169 const LOGLVL_WARNING = 3;
2175 const LOGLVL_ERROR = 2;
2180 const LOGLVL_FATAL = 1;
2185 const LOGFILE_MAX_LINE_LENGTH = 2048;
2190 # ---- Event Handling ---------------------------------------------------- 2197 const EVENTTYPE_DEFAULT = 1;
2203 const EVENTTYPE_CHAIN = 2;
2209 const EVENTTYPE_FIRST = 3;
2217 const EVENTTYPE_NAMED = 4;
2220 const ORDER_FIRST = 1;
2222 const ORDER_MIDDLE = 2;
2224 const ORDER_LAST = 3;
2234 public function RegisterEvent($EventsOrEventName, $EventType = NULL)
2236 # convert parameters to array if not already in that form 2237 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2238 : array($EventsOrEventName => $EventType);
2241 foreach ($Events as $Name => $Type)
2243 # store event information 2244 $this->RegisteredEvents[$Name][
"Type"] = $Type;
2245 $this->RegisteredEvents[$Name][
"Hooks"] = array();
2255 public function IsRegisteredEvent($EventName)
2257 return array_key_exists($EventName, $this->RegisteredEvents)
2267 public function IsHookedEvent($EventName)
2269 # the event isn't hooked to if it isn't even registered 2270 if (!$this->IsRegisteredEvent($EventName))
2275 # return TRUE if there is at least one callback hooked to the event 2276 return count($this->RegisteredEvents[$EventName][
"Hooks"]) > 0;
2292 public function HookEvent(
2293 $EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
2295 # convert parameters to array if not already in that form 2296 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2297 : array($EventsOrEventName => $Callback);
2301 foreach ($Events as $EventName => $EventCallback)
2303 # if callback is valid 2304 if (is_callable($EventCallback))
2306 # if this is a periodic event we process internally 2307 if (isset($this->PeriodicEvents[$EventName]))
2310 $this->ProcessPeriodicEvent($EventName, $EventCallback);
2312 # if specified event has been registered 2313 elseif (isset($this->RegisteredEvents[$EventName]))
2315 # add callback for event 2316 $this->RegisteredEvents[$EventName][
"Hooks"][]
2317 = array(
"Callback" => $EventCallback,
"Order" => $Order);
2319 # sort callbacks by order 2320 if (count($this->RegisteredEvents[$EventName][
"Hooks"]) > 1)
2322 usort($this->RegisteredEvents[$EventName][
"Hooks"],
2323 array(
"ApplicationFramework",
"HookEvent_OrderCompare"));
2337 # report to caller whether all callbacks were hooked 2346 private static function HookEvent_OrderCompare($A, $B)
2348 if ($A[
"Order"] == $B[
"Order"]) {
return 0; }
2349 return ($A[
"Order"] < $B[
"Order"]) ? -1 : 1;
2362 public function SignalEvent($EventName, $Parameters = NULL)
2364 $ReturnValue = NULL;
2366 # if event has been registered 2367 if (isset($this->RegisteredEvents[$EventName]))
2369 # set up default return value (if not NULL) 2370 switch ($this->RegisteredEvents[$EventName][
"Type"])
2372 case self::EVENTTYPE_CHAIN:
2373 $ReturnValue = $Parameters;
2376 case self::EVENTTYPE_NAMED:
2377 $ReturnValue = array();
2381 # for each callback for this event 2382 foreach ($this->RegisteredEvents[$EventName][
"Hooks"] as $Hook)
2385 $Callback = $Hook[
"Callback"];
2386 $Result = ($Parameters !== NULL)
2387 ? call_user_func_array($Callback, $Parameters)
2388 : call_user_func($Callback);
2390 # process return value based on event type 2391 switch ($this->RegisteredEvents[$EventName][
"Type"])
2393 case self::EVENTTYPE_CHAIN:
2394 if ($Result !== NULL)
2396 foreach ($Parameters as $Index => $Value)
2398 if (array_key_exists($Index, $Result))
2400 $Parameters[$Index] = $Result[$Index];
2403 $ReturnValue = $Parameters;
2407 case self::EVENTTYPE_FIRST:
2408 if ($Result !== NULL)
2410 $ReturnValue = $Result;
2415 case self::EVENTTYPE_NAMED:
2416 $CallbackName = is_array($Callback)
2417 ? (is_object($Callback[0])
2418 ? get_class($Callback[0])
2419 : $Callback[0]).
"::".$Callback[1]
2421 $ReturnValue[$CallbackName] = $Result;
2431 $this->LogError(self::LOGLVL_WARNING,
2432 "Unregistered event (".$EventName.
") signaled by " 2433 .StdLib::GetMyCaller().
".");
2436 # return value if any to caller 2437 return $ReturnValue;
2445 public function IsStaticOnlyEvent($EventName)
2447 return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
2460 public function EventWillNextRunAt($EventName, $Callback)
2462 # if event is not a periodic event report failure to caller 2463 if (!array_key_exists($EventName, $this->EventPeriods)) {
return FALSE; }
2465 # retrieve last execution time for event if available 2466 $Signature = self::GetCallbackSignature($Callback);
2467 $LastRunTime = $this->DB->Query(
"SELECT LastRunAt FROM PeriodicEvents" 2468 .
" WHERE Signature = '".addslashes($Signature).
"'",
"LastRunAt");
2470 # if event was not found report failure to caller 2471 if ($LastRunTime === NULL) {
return FALSE; }
2473 # calculate next run time based on event period 2474 $NextRunTime = strtotime($LastRunTime) + $this->EventPeriods[$EventName];
2476 # report next run time to caller 2477 return $NextRunTime;
2495 public function GetKnownPeriodicEvents()
2497 # retrieve last execution times 2498 $this->DB->Query(
"SELECT * FROM PeriodicEvents");
2499 $LastRunTimes = $this->DB->FetchColumn(
"LastRunAt",
"Signature");
2501 # for each known event 2503 foreach ($this->KnownPeriodicEvents as $Signature => $Info)
2505 # if last run time for event is available 2506 if (array_key_exists($Signature, $LastRunTimes))
2508 # calculate next run time for event 2509 $LastRun = strtotime($LastRunTimes[$Signature]);
2510 $NextRun = $LastRun + $this->EventPeriods[$Info[
"Period"]];
2511 if ($Info[
"Period"] ==
"EVENT_PERIODIC") { $LastRun = FALSE; }
2515 # set info to indicate run times are not known 2520 # add event info to list 2521 $Events[$Signature] = $Info;
2522 $Events[$Signature][
"LastRun"] = $LastRun;
2523 $Events[$Signature][
"NextRun"] = $NextRun;
2524 $Events[$Signature][
"Parameters"] = NULL;
2527 # return list of known events to caller 2534 # ---- Task Management --------------------------------------------------- 2539 const PRIORITY_HIGH = 1;
2541 const PRIORITY_MEDIUM = 2;
2543 const PRIORITY_LOW = 3;
2545 const PRIORITY_BACKGROUND = 4;
2559 public function QueueTask($Callback, $Parameters = NULL,
2560 $Priority = self::PRIORITY_LOW, $Description =
"")
2562 # pack task info and write to database 2563 if ($Parameters === NULL) { $Parameters = array(); }
2564 $this->DB->Query(
"INSERT INTO TaskQueue" 2565 .
" (Callback, Parameters, Priority, Description)" 2566 .
" VALUES ('".addslashes(serialize($Callback)).
"', '" 2567 .addslashes(serialize($Parameters)).
"', ".intval($Priority).
", '" 2568 .addslashes($Description).
"')");
2588 public function QueueUniqueTask($Callback, $Parameters = NULL,
2589 $Priority = self::PRIORITY_LOW, $Description =
"")
2591 if ($this->TaskIsInQueue($Callback, $Parameters))
2593 $QueryResult = $this->DB->Query(
"SELECT TaskId,Priority FROM TaskQueue" 2594 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'" 2595 .($Parameters ?
" AND Parameters = '" 2596 .addslashes(serialize($Parameters)).
"'" :
""));
2597 if ($QueryResult !== FALSE)
2599 $Record = $this->DB->FetchRow();
2600 if ($Record[
"Priority"] > $Priority)
2602 $this->DB->Query(
"UPDATE TaskQueue" 2603 .
" SET Priority = ".intval($Priority)
2604 .
" WHERE TaskId = ".intval($Record[
"TaskId"]));
2611 $this->QueueTask($Callback, $Parameters, $Priority, $Description);
2625 public function TaskIsInQueue($Callback, $Parameters = NULL)
2627 $QueuedCount = $this->DB->Query(
2628 "SELECT COUNT(*) AS FoundCount FROM TaskQueue" 2629 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'" 2630 .($Parameters ?
" AND Parameters = '" 2631 .addslashes(serialize($Parameters)).
"'" :
""),
2633 $RunningCount = $this->DB->Query(
2634 "SELECT COUNT(*) AS FoundCount FROM RunningTasks" 2635 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'" 2636 .($Parameters ?
" AND Parameters = '" 2637 .addslashes(serialize($Parameters)).
"'" :
""),
2639 $FoundCount = $QueuedCount + $RunningCount;
2640 return ($FoundCount ? TRUE : FALSE);
2648 public function GetTaskQueueSize($Priority = NULL)
2650 return $this->GetQueuedTaskCount(NULL, NULL, $Priority);
2660 public function GetQueuedTaskList($Count = 100, $Offset = 0)
2662 return $this->GetTaskList(
"SELECT * FROM TaskQueue" 2663 .
" ORDER BY Priority, TaskId ", $Count, $Offset);
2679 public function GetQueuedTaskCount($Callback = NULL,
2680 $Parameters = NULL, $Priority = NULL, $Description = NULL)
2682 $Query =
"SELECT COUNT(*) AS TaskCount FROM TaskQueue";
2684 if ($Callback !== NULL)
2686 $Query .= $Sep.
" Callback = '".addslashes(serialize($Callback)).
"'";
2689 if ($Parameters !== NULL)
2691 $Query .= $Sep.
" Parameters = '".addslashes(serialize($Parameters)).
"'";
2694 if ($Priority !== NULL)
2696 $Query .= $Sep.
" Priority = ".intval($Priority);
2699 if ($Description !== NULL)
2701 $Query .= $Sep.
" Description = '".addslashes($Description).
"'";
2703 return $this->DB->Query($Query,
"TaskCount");
2713 public function GetRunningTaskList($Count = 100, $Offset = 0)
2715 return $this->GetTaskList(
"SELECT * FROM RunningTasks" 2716 .
" WHERE StartedAt >= '".date(
"Y-m-d H:i:s",
2717 (time() - ini_get(
"max_execution_time"))).
"'" 2718 .
" ORDER BY StartedAt", $Count, $Offset);
2728 public function GetOrphanedTaskList($Count = 100, $Offset = 0)
2730 return $this->GetTaskList(
"SELECT * FROM RunningTasks" 2731 .
" WHERE StartedAt < '".date(
"Y-m-d H:i:s",
2732 (time() - ini_get(
"max_execution_time"))).
"'" 2733 .
" ORDER BY StartedAt", $Count, $Offset);
2740 public function GetOrphanedTaskCount()
2742 return $this->DB->Query(
"SELECT COUNT(*) AS Count FROM RunningTasks" 2743 .
" WHERE StartedAt < '".date(
"Y-m-d H:i:s",
2744 (time() - ini_get(
"max_execution_time"))).
"'",
2753 public function ReQueueOrphanedTask($TaskId, $NewPriority = NULL)
2755 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
2756 $this->DB->Query(
"INSERT INTO TaskQueue" 2757 .
" (Callback,Parameters,Priority,Description) " 2758 .
"SELECT Callback, Parameters, Priority, Description" 2759 .
" FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2760 if ($NewPriority !== NULL)
2762 $NewTaskId = $this->DB->LastInsertId();
2763 $this->DB->Query(
"UPDATE TaskQueue SET Priority = " 2764 .intval($NewPriority)
2765 .
" WHERE TaskId = ".intval($NewTaskId));
2767 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2768 $this->DB->Query(
"UNLOCK TABLES");
2775 public function DeleteTask($TaskId)
2777 $this->DB->Query(
"DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2778 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2788 public function GetTask($TaskId)
2790 # assume task will not be found 2793 # look for task in task queue 2794 $this->DB->Query(
"SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2796 # if task was not found in queue 2797 if (!$this->DB->NumRowsSelected())
2799 # look for task in running task list 2800 $this->DB->Query(
"SELECT * FROM RunningTasks WHERE TaskId = " 2805 if ($this->DB->NumRowsSelected())
2807 # if task was periodic 2808 $Row = $this->DB->FetchRow();
2809 if ($Row[
"Callback"] ==
2810 serialize(array(
"ApplicationFramework",
"PeriodicEventWrapper")))
2812 # unpack periodic task callback 2813 $WrappedCallback = unserialize($Row[
"Parameters"]);
2814 $Task[
"Callback"] = $WrappedCallback[1];
2815 $Task[
"Parameters"] = $WrappedCallback[2];
2819 # unpack task callback and parameters 2820 $Task[
"Callback"] = unserialize($Row[
"Callback"]);
2821 $Task[
"Parameters"] = unserialize($Row[
"Parameters"]);
2825 # return task to caller 2836 public function TaskExecutionEnabled($NewValue =
DB_NOVALUE)
2838 return $this->UpdateSetting(
"TaskExecutionEnabled", $NewValue);
2846 public function MaxTasks($NewValue =
DB_NOVALUE)
2848 return $this->UpdateSetting(
"MaxTasksRunning", $NewValue);
2858 public static function GetTaskCallbackSynopsis($TaskInfo)
2860 # if task callback is function use function name 2861 $Callback = $TaskInfo[
"Callback"];
2863 if (!is_array($Callback))
2869 # if task callback is object 2870 if (is_object($Callback[0]))
2872 # if task callback is encapsulated ask encapsulation for name 2873 if (method_exists($Callback[0],
"GetCallbackAsText"))
2875 $Name = $Callback[0]->GetCallbackAsText();
2877 # else assemble name from object 2880 $Name = get_class($Callback[0]) .
"::" . $Callback[1];
2883 # else assemble name from supplied info 2886 $Name= $Callback[0] .
"::" . $Callback[1];
2890 # if parameter array was supplied 2891 $Parameters = $TaskInfo[
"Parameters"];
2892 $ParameterString =
"";
2893 if (is_array($Parameters))
2895 # assemble parameter string 2897 foreach ($Parameters as $Parameter)
2899 $ParameterString .= $Separator;
2900 if (is_int($Parameter) || is_float($Parameter))
2902 $ParameterString .= $Parameter;
2904 else if (is_string($Parameter))
2906 $ParameterString .=
"\"".htmlspecialchars($Parameter).
"\"";
2908 else if (is_array($Parameter))
2910 $ParameterString .=
"ARRAY";
2912 else if (is_object($Parameter))
2914 $ParameterString .=
"OBJECT";
2916 else if (is_null($Parameter))
2918 $ParameterString .=
"NULL";
2920 else if (is_bool($Parameter))
2922 $ParameterString .= $Parameter ?
"TRUE" :
"FALSE";
2924 else if (is_resource($Parameter))
2926 $ParameterString .= get_resource_type($Parameter);
2930 $ParameterString .=
"????";
2936 # assemble name and parameters and return result to caller 2937 return $Name.
"(".$ParameterString.
")";
2944 public function IsRunningInBackground()
2946 return $this->RunningInBackground;
2954 public function GetCurrentBackgroundPriority()
2956 return isset($this->RunningTask)
2957 ? $this->RunningTask[
"Priority"] : NULL;
2968 public function GetNextHigherBackgroundPriority($Priority = NULL)
2970 if ($Priority === NULL)
2972 $Priority = $this->GetCurrentBackgroundPriority();
2973 if ($Priority === NULL)
2978 return ($Priority > self::PRIORITY_HIGH)
2979 ? ($Priority - 1) : self::PRIORITY_HIGH;
2990 public function GetNextLowerBackgroundPriority($Priority = NULL)
2992 if ($Priority === NULL)
2994 $Priority = $this->GetCurrentBackgroundPriority();
2995 if ($Priority === NULL)
3000 return ($Priority < self::PRIORITY_BACKGROUND)
3001 ? ($Priority + 1) : self::PRIORITY_BACKGROUND;
3007 # ---- Clean URL Support ------------------------------------------------- 3037 public function AddCleanUrl($Pattern, $Page, $GetVars = NULL, $Template = NULL)
3039 # save clean URL mapping parameters 3040 $this->CleanUrlMappings[] = array(
3041 "Pattern" => $Pattern,
3043 "GetVars" => $GetVars,
3046 # if replacement template specified 3047 if ($Template !== NULL)
3049 # if GET parameters specified 3050 if (count($GetVars))
3052 # retrieve all possible permutations of GET parameters 3053 $GetPerms = $this->ArrayPermutations(array_keys($GetVars));
3055 # for each permutation of GET parameters 3056 foreach ($GetPerms as $VarPermutation)
3058 # construct search pattern for permutation 3059 $SearchPattern =
"/href=([\"'])index\\.php\\?P=".$Page;
3060 $GetVarSegment =
"";
3061 foreach ($VarPermutation as $GetVar)
3063 if (preg_match(
"%\\\$[0-9]+%", $GetVars[$GetVar]))
3065 $GetVarSegment .=
"&".$GetVar.
"=((?:(?!\\1)[^&])+)";
3069 $GetVarSegment .=
"&".$GetVar.
"=".$GetVars[$GetVar];
3072 $SearchPattern .= $GetVarSegment.
"\\1/i";
3074 # if template is actually a callback 3075 if (is_callable($Template))
3077 # add pattern to HTML output mod callbacks list 3078 $this->OutputModificationCallbacks[] = array(
3079 "Pattern" => $Pattern,
3081 "SearchPattern" => $SearchPattern,
3082 "Callback" => $Template,
3087 # construct replacement string for permutation 3088 $Replacement = $Template;
3090 foreach ($VarPermutation as $GetVar)
3092 $Replacement = str_replace(
3093 "\$".$GetVar,
"\$".$Index, $Replacement);
3096 $Replacement =
"href=\"".$Replacement.
"\"";
3098 # add pattern to HTML output modifications list 3099 $this->OutputModificationPatterns[] = $SearchPattern;
3100 $this->OutputModificationReplacements[] = $Replacement;
3106 # construct search pattern 3107 $SearchPattern =
"/href=\"index\\.php\\?P=".$Page.
"\"/i";
3109 # if template is actually a callback 3110 if (is_callable($Template))
3112 # add pattern to HTML output mod callbacks list 3113 $this->OutputModificationCallbacks[] = array(
3114 "Pattern" => $Pattern,
3116 "SearchPattern" => $SearchPattern,
3117 "Callback" => $Template,
3122 # add simple pattern to HTML output modifications list 3123 $this->OutputModificationPatterns[] = $SearchPattern;
3124 $this->OutputModificationReplacements[] =
"href=\"".$Template.
"\"";
3135 public function CleanUrlIsMapped($Path)
3137 foreach ($this->CleanUrlMappings as $Info)
3139 if (preg_match($Info[
"Pattern"], $Path))
3156 public function GetCleanUrlForPath($Path)
3158 # the search patterns and callbacks require a specific format 3159 $Format =
"href=\"".str_replace(
"&",
"&", $Path).
"\"";
3162 # perform any regular expression replacements on the search string 3163 $Search = preg_replace($this->OutputModificationPatterns,
3164 $this->OutputModificationReplacements, $Search);
3166 # only run the callbacks if a replacement hasn't already been performed 3167 if ($Search == $Format)
3169 # perform any callback replacements on the search string 3170 foreach ($this->OutputModificationCallbacks as $Info)
3172 # make the information available to the callback 3173 $this->OutputModificationCallbackInfo = $Info;
3175 # execute the callback 3176 $Search = preg_replace_callback($Info[
"SearchPattern"],
3177 array($this,
"OutputModificationCallbackShell"),
3182 # return the path untouched if no replacements were performed 3183 if ($Search == $Format)
3188 # remove the bits added to the search string to get it recognized by 3189 # the replacement expressions and callbacks 3190 $Result = substr($Search, 6, -1);
3201 public function GetUncleanUrlForPath($Path)
3203 # for each clean URL mapping 3204 foreach ($this->CleanUrlMappings as $Info)
3206 # if current path matches the clean URL pattern 3207 if (preg_match($Info[
"Pattern"], $Path, $Matches))
3209 # the GET parameters for the URL, starting with the page name 3210 $GetVars = array(
"P" => $Info[
"Page"]);
3212 # if additional $_GET variables specified for clean URL 3213 if ($Info[
"GetVars"] !== NULL)
3215 # for each $_GET variable specified for clean URL 3216 foreach ($Info[
"GetVars"] as $VarName => $VarTemplate)
3218 # start with template for variable value 3219 $Value = $VarTemplate;
3221 # for each subpattern matched in current URL 3222 foreach ($Matches as $Index => $Match)
3224 # if not first (whole) match 3227 # make any substitutions in template 3228 $Value = str_replace(
"$".$Index, $Match, $Value);
3232 # add the GET variable 3233 $GetVars[$VarName] = $Value;
3237 # return the unclean URL 3238 return "index.php?" . http_build_query($GetVars);
3242 # return the path unchanged 3251 public function GetCleanUrl()
3253 return $this->GetCleanUrlForPath($this->GetUncleanUrl());
3260 public function GetUncleanUrl()
3262 $GetVars = array(
"P" => $this->GetPageName()) + $_GET;
3263 return "index.php?" . http_build_query($GetVars);
3277 public function AddPrefixForAlternateDomain($Domain, $Prefix)
3279 $this->AlternateDomainPrefixes[$Domain] = $Prefix;
3284 # ---- Server Environment ------------------------------------------------ 3293 public static function SessionLifetime($NewValue = NULL)
3295 if ($NewValue !== NULL)
3297 self::$SessionLifetime = $NewValue;
3299 return self::$SessionLifetime;
3307 public static function HtaccessSupport()
3309 return isset($_SERVER[
"HTACCESS_SUPPORT"])
3310 || isset($_SERVER[
"REDIRECT_HTACCESS_SUPPORT"]);
3319 public static function UrlFingerprintingRewriteSupport()
3321 return isset($_SERVER[
"URL_FINGERPRINTING_SUPPORT"])
3322 || isset($_SERVER[
"REDIRECT_URL_FINGERPRINTING_SUPPORT"]);
3331 public static function ScssRewriteSupport()
3333 return isset($_SERVER[
"SCSS_REWRITE_SUPPORT"])
3334 || isset($_SERVER[
"REDIRECT_SCSS_REWRITE_SUPPORT"]);
3343 public static function JsMinRewriteSupport()
3345 return isset($_SERVER[
"JSMIN_REWRITE_SUPPORT"])
3346 || isset($_SERVER[
"REDIRECT_JSMIN_REWRITE_SUPPORT"]);
3356 public static function RootUrl()
3358 # return override value if one is set 3359 if (self::$RootUrlOverride !== NULL)
3361 return self::$RootUrlOverride;
3364 # determine scheme name 3365 $Protocol = (isset($_SERVER[
"HTTPS"]) ?
"https" :
"http");
3367 # if HTTP_HOST is preferred or SERVER_NAME points to localhost 3368 # and HTTP_HOST is set 3369 if ((self::$PreferHttpHost || ($_SERVER[
"SERVER_NAME"] ==
"127.0.0.1"))
3370 && isset($_SERVER[
"HTTP_HOST"]))
3372 # use HTTP_HOST for domain name 3373 $DomainName = $_SERVER[
"HTTP_HOST"];
3377 # use SERVER_NAME for domain name 3378 $DomainName = $_SERVER[
"HTTP_HOST"];
3381 # build URL root and return to caller 3382 return $Protocol.
"://".$DomainName;
3399 public static function RootUrlOverride($NewValue = self::NOVALUE)
3401 if ($NewValue !== self::NOVALUE)
3403 self::$RootUrlOverride = strlen(trim($NewValue)) ? $NewValue : NULL;
3405 return self::$RootUrlOverride;
3417 public static function BaseUrl()
3419 $BaseUrl = self::RootUrl().dirname($_SERVER[
"SCRIPT_NAME"]);
3420 if (substr($BaseUrl, -1) !=
"/") { $BaseUrl .=
"/"; }
3431 public static function FullUrl()
3433 return self::RootUrl().$_SERVER[
"REQUEST_URI"];
3446 public static function PreferHttpHost($NewValue = NULL)
3448 if ($NewValue !== NULL)
3450 self::$PreferHttpHost = ($NewValue ? TRUE : FALSE);
3452 return self::$PreferHttpHost;
3459 public static function BasePath()
3461 $BasePath = dirname($_SERVER[
"SCRIPT_NAME"]);
3463 if (substr($BasePath, -1) !=
"/")
3476 public static function GetScriptUrl()
3478 if (array_key_exists(
"SCRIPT_URL", $_SERVER))
3480 return $_SERVER[
"SCRIPT_URL"];
3482 elseif (array_key_exists(
"REDIRECT_URL", $_SERVER))
3484 return $_SERVER[
"REDIRECT_URL"];
3486 elseif (array_key_exists(
"REQUEST_URI", $_SERVER))
3488 $Pieces = parse_url($_SERVER[
"REQUEST_URI"]);
3489 return isset($Pieces[
"path"]) ? $Pieces[
"path"] : NULL;
3505 public static function WasUrlRewritten($ScriptName=
"index.php")
3507 # needed to get the path of the URL minus the query and fragment pieces 3508 $Components = parse_url(self::GetScriptUrl());
3510 # if parsing was successful and a path is set 3511 if (is_array($Components) && isset($Components[
"path"]))
3513 $BasePath = self::BasePath();
3514 $Path = $Components[
"path"];
3516 # the URL was rewritten if the path isn't the base path, i.e., the 3517 # home page, and the file in the URL isn't the script generating the 3519 if ($BasePath != $Path && basename($Path) != $ScriptName)
3525 # the URL wasn't rewritten 3534 public static function GetFreeMemory()
3536 return self::GetPhpMemoryLimit() - memory_get_usage();
3544 public static function GetPhpMemoryLimit()
3546 $Str = strtoupper(ini_get(
"memory_limit"));
3547 if (substr($Str, -1) ==
"B") { $Str = substr($Str, 0, strlen($Str) - 1); }
3548 switch (substr($Str, -1))
3551 $MemoryLimit = (int)$Str * 1024;
3555 $MemoryLimit = (int)$Str * 1048576;
3559 $MemoryLimit = (int)$Str * 1073741824;
3563 $MemoryLimit = (int)$Str;
3566 return $MemoryLimit;
3576 public function MaxExecutionTime($NewValue = NULL)
3578 if (func_num_args() && !ini_get(
"safe_mode"))
3580 if ($NewValue != $this->Settings[
"MaxExecTime"])
3582 $this->Settings[
"MaxExecTime"] = max($NewValue, 5);
3583 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings" 3584 .
" SET MaxExecTime = '" 3585 .intval($this->Settings[
"MaxExecTime"]).
"'");
3587 ini_set(
"max_execution_time", $this->Settings[
"MaxExecTime"]);
3588 set_time_limit($this->Settings[
"MaxExecTime"]);
3590 return ini_get(
"max_execution_time");
3596 # ---- Utility ----------------------------------------------------------- 3611 public function DownloadFile($FilePath, $FileName = NULL, $MimeType = NULL)
3613 # check that file is readable 3614 if (!is_readable($FilePath))
3619 # if file name was not supplied 3620 if ($FileName === NULL)
3622 # extract file name from path 3623 $FileName = basename($FilePath);
3626 # if MIME type was not supplied 3627 if ($MimeType === NULL)
3629 # attempt to determine MIME type 3630 $FInfoHandle = finfo_open(FILEINFO_MIME);
3633 $FInfoMime = finfo_file($FInfoHandle, $FilePath);
3634 finfo_close($FInfoHandle);
3637 $MimeType = $FInfoMime;
3641 # use default if unable to determine MIME type 3642 if ($MimeType === NULL)
3644 $MimeType =
"application/octet-stream";
3648 # set headers to download file 3649 header(
"Content-Type: ".$MimeType);
3650 header(
"Content-Length: ".filesize($FilePath));
3651 if ($this->CleanUrlRewritePerformed)
3653 header(
'Content-Disposition: attachment; filename="'.$FileName.
'"');
3656 # make sure that apache does not attempt to compress file 3657 apache_setenv(
'no-gzip',
'1');
3659 # send file to user, but unbuffered to avoid memory issues 3660 $this->AddUnbufferedCallback(
function ($File)
3662 $BlockSize = 512000;
3663 $Handle = @fopen($File,
"rb");
3664 if ($Handle === FALSE)
3668 while (!feof($Handle))
3670 print fread($Handle, $BlockSize);
3674 }, array($FilePath));
3676 # prevent HTML output that might interfere with download 3677 $this->SuppressHTMLOutput();
3679 # set flag to indicate not to log a slow page load in case client 3680 # connection delays PHP execution because of header 3681 $this->DoNotLogSlowPageLoad = TRUE;
3683 # report no errors found to caller 3690 # ---- Backward Compatibility -------------------------------------------- 3700 public function FindCommonTemplate($BaseName)
3702 return $this->FindFile(
3703 $this->IncludeDirList, $BaseName, array(
"tpl",
"html"));
3709 # ---- PRIVATE INTERFACE ------------------------------------------------- 3711 private $AdditionalRequiredUIFiles = array();
3712 private $BackgroundTaskMemLeakLogThreshold = 10; # percentage of max mem
3713 private $BackgroundTaskMinFreeMemPercent = 25;
3714 private $BrowserDetectFunc;
3715 private $CacheCurrentPage = TRUE;
3716 private $AlternateDomainPrefixes = array();
3717 private $CleanUrlMappings = array();
3718 private $CleanUrlRewritePerformed = FALSE;
3719 private $CssUrlFingerprintPath;
3721 private $DefaultPage =
"Home";
3722 private $DoNotMinimizeList = array();
3723 private $DoNotLogSlowPageLoad = FALSE;
3724 private $EnvIncludes = array();
3725 private $ExecutionStartTime;
3726 private $FoundUIFiles = array();
3727 private $HtmlCharset =
"UTF-8";
3728 private $JSMinimizerJavaScriptPackerAvailable = FALSE;
3729 private $JSMinimizerJShrinkAvailable = TRUE;
3730 private $JumpToPage = NULL;
3731 private $JumpToPageDelay = 0;
3732 private $LogFileName =
"local/logs/site.log";
3733 private $MaxRunningTasksToTrack = 250;
3734 private $OutputModificationCallbackInfo;
3735 private $OutputModificationCallbacks = array();
3736 private $OutputModificationPatterns = array();
3737 private $OutputModificationReplacements = array();
3738 private $PageCacheTags = array();
3740 private $PostProcessingFuncs = array();
3741 private $RunningInBackground = FALSE;
3742 private $RunningTask;
3743 private $SavedContext;
3744 private $SaveTemplateLocationCache = FALSE;
3745 private $SessionStorage;
3746 private $SessionGcProbability;
3748 private $SuppressHTML = FALSE;
3749 private $TemplateLocationCache;
3750 private $TemplateLocationCacheInterval = 60; # in minutes
3751 private $TemplateLocationCacheExpiration;
3752 private $UnbufferedCallbacks = array();
3753 private $UrlFingerprintBlacklist = array();
3754 private $UseBaseTag = FALSE;
3756 private static $ActiveUI =
"default";
3757 private static $AppName =
"ScoutAF";
3758 private static $DefaultUI =
"default";
3759 private static $JSMinCacheDir =
"local/data/caches/JSMin";
3760 private static $ObjectDirectories = array();
3761 private static $ObjectLocationCache;
3762 private static $ObjectLocationCacheInterval = 60;
3763 private static $ObjectLocationCacheExpiration;
3764 private static $PreferHttpHost = FALSE;
3765 private static $RootUrlOverride = NULL;
3766 private static $SaveObjectLocationCache = FALSE;
3767 private static $ScssCacheDir =
"local/data/caches/SCSS";
3768 private static $SessionLifetime = 1440; # in seconds
3770 # offset used to generate page cache tag IDs from numeric tags 3771 const PAGECACHETAGIDOFFSET = 100000;
3777 private $NoTSR = FALSE;
3779 private $KnownPeriodicEvents = array();
3780 private $PeriodicEvents = array(
3781 "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
3782 "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
3783 "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
3784 "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
3785 "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
3787 private $EventPeriods = array(
3788 "EVENT_HOURLY" => 3600,
3789 "EVENT_DAILY" => 86400,
3790 "EVENT_WEEKLY" => 604800,
3791 "EVENT_MONTHLY" => 2592000,
3792 "EVENT_PERIODIC" => 0,
3794 private $UIEvents = array(
3795 "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
3796 "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
3797 "EVENT_PHP_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
3798 "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
3799 "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
3800 "EVENT_PAGE_OUTPUT_FILTER" => self::EVENTTYPE_CHAIN,
3807 private function LoadSettings()
3809 # read settings in from database 3810 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
3811 $this->Settings = $this->DB->FetchRow();
3813 # if settings were not previously initialized 3814 if ($this->Settings === FALSE)
3816 # initialize settings in database 3817 $this->DB->Query(
"INSERT INTO ApplicationFrameworkSettings" 3818 .
" (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
3820 # read new settings in from database 3821 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
3822 $this->Settings = $this->DB->FetchRow();
3824 # bail out if reloading new settings failed 3825 if ($this->Settings === FALSE)
3827 throw new Exception(
3828 "Unable to load application framework settings.");
3832 # if base path was not previously set or we appear to have moved 3833 if (!array_key_exists(
"BasePath", $this->Settings)
3834 || (!strlen($this->Settings[
"BasePath"]))
3835 || (!array_key_exists(
"BasePathCheck", $this->Settings))
3836 || (__FILE__ != $this->Settings[
"BasePathCheck"]))
3838 # attempt to extract base path from Apache .htaccess file 3839 if (is_readable(
".htaccess"))
3841 $Lines = file(
".htaccess");
3842 foreach ($Lines as $Line)
3844 if (preg_match(
"/\\s*RewriteBase\\s+/", $Line))
3846 $Pieces = preg_split(
3847 "/\\s+/", $Line, NULL, PREG_SPLIT_NO_EMPTY);
3848 $BasePath = $Pieces[1];
3853 # if base path was found 3854 if (isset($BasePath))
3856 # save base path locally 3857 $this->Settings[
"BasePath"] = $BasePath;
3859 # save base path to database 3860 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings" 3861 .
" SET BasePath = '".addslashes($BasePath).
"'" 3862 .
", BasePathCheck = '".addslashes(__FILE__).
"'");
3866 # retrieve template location cache 3867 $this->TemplateLocationCache = unserialize(
3868 $this->Settings[
"TemplateLocationCache"]);
3869 $this->TemplateLocationCacheInterval =
3870 $this->Settings[
"TemplateLocationCacheInterval"];
3871 $this->TemplateLocationCacheExpiration =
3872 strtotime($this->Settings[
"TemplateLocationCacheExpiration"]);
3874 # if template location cache looks invalid or has expired 3875 $CurrentTime = time();
3876 if (!count($this->TemplateLocationCache)
3877 || ($CurrentTime >= $this->TemplateLocationCacheExpiration))
3879 # clear cache and reset cache expiration 3880 $this->TemplateLocationCache = array();
3881 $this->TemplateLocationCacheExpiration =
3882 $CurrentTime + ($this->TemplateLocationCacheInterval * 60);
3883 $this->SaveTemplateLocationCache = TRUE;
3886 # retrieve object location cache 3887 self::$ObjectLocationCache =
3888 unserialize($this->Settings[
"ObjectLocationCache"]);
3889 self::$ObjectLocationCacheInterval =
3890 $this->Settings[
"ObjectLocationCacheInterval"];
3891 self::$ObjectLocationCacheExpiration =
3892 strtotime($this->Settings[
"ObjectLocationCacheExpiration"]);
3894 # if object location cache looks invalid or has expired 3895 if (!count(self::$ObjectLocationCache)
3896 || ($CurrentTime >= self::$ObjectLocationCacheExpiration))
3898 # clear cache and reset cache expiration 3899 self::$ObjectLocationCache = array();
3900 self::$ObjectLocationCacheExpiration =
3901 $CurrentTime + (self::$ObjectLocationCacheInterval * 60);
3902 self::$SaveObjectLocationCache = TRUE;
3912 private function RewriteCleanUrls($PageName)
3914 # if URL rewriting is supported by the server 3915 if ($this->HtaccessSupport())
3917 # retrieve current URL and remove base path if present 3918 $Url = $this->GetPageLocation();
3920 # for each clean URL mapping 3921 foreach ($this->CleanUrlMappings as $Info)
3923 # if current URL matches clean URL pattern 3924 if (preg_match($Info[
"Pattern"], $Url, $Matches))
3927 $PageName = $Info[
"Page"];
3929 # if $_GET variables specified for clean URL 3930 if ($Info[
"GetVars"] !== NULL)
3932 # for each $_GET variable specified for clean URL 3933 foreach ($Info[
"GetVars"] as $VarName => $VarTemplate)
3935 # start with template for variable value 3936 $Value = $VarTemplate;
3938 # for each subpattern matched in current URL 3939 foreach ($Matches as $Index => $Match)
3941 # if not first (whole) match 3944 # make any substitutions in template 3945 $Value = str_replace(
"$".$Index, $Match, $Value);
3949 # set $_GET variable 3950 $_GET[$VarName] = $Value;
3954 # set flag indicating clean URL mapped 3955 $this->CleanUrlRewritePerformed = TRUE;
3957 # stop looking for a mapping 3963 # return (possibly) updated page name to caller 3979 private function RewriteAlternateDomainUrls($Html)
3981 if ($this->HtaccessSupport() &&
3982 self::$RootUrlOverride !== NULL)
3984 $VHost = $_SERVER[
"SERVER_NAME"];
3985 if (isset($this->AlternateDomainPrefixes[$VHost]))
3987 $ThisPrefix = $this->AlternateDomainPrefixes[$VHost];
3989 # get the URL for the primary domain 3990 $RootUrl = $this->RootUrl().
"/";
3992 # and figure out what protcol we were using 3993 $Protocol = (isset($_SERVER[
"HTTPS"]) ?
"https" :
"http");
3995 # convert all relative URLs absolute URLs within our 3996 # primary domain, then substitute in the alternate domain 3997 # for paths inside our configured prefix 3998 $RelativePathPatterns = array(
3999 "%src=\"(?!http://|https://)%i",
4000 "%src='(?!http://|https://)%i",
4001 "%href=\"(?!http://|https://)%i",
4002 "%href='(?!http://|https://)%i",
4003 "%action=\"(?!http://|https://)%i",
4004 "%action='(?!http://|https://)%i",
4005 "%@import\s+url\(\"(?!http://|https://)%i",
4006 "%@import\s+url\('(?!http://|https://)%i",
4007 "%src:\s+url\(\"(?!http://|https://)%i",
4008 "%src:\s+url\('(?!http://|https://)%i",
4009 "%@import\s+\"(?!http://|https://)%i",
4010 "%@import\s+'(?!http://|https://)%i",
4011 "%".preg_quote($RootUrl.$ThisPrefix.
"/",
"%").
"%",
4013 $RelativePathReplacements = array(
4018 "action=\"".$RootUrl,
4019 "action='".$RootUrl,
4020 "@import url(\"".$RootUrl,
4021 "@import url('".$RootUrl,
4022 "src: url(\"".$RootUrl,
4023 "src: url('".$RootUrl,
4024 "@import \"".$RootUrl,
4025 "@import '".$RootUrl,
4026 $Protocol.
"://".$VHost.
"/",
4029 $NewHtml = preg_replace(
4030 $RelativePathPatterns,
4031 $RelativePathReplacements,
4034 # check to make sure relative path fixes didn't fail 4035 $Html = $this->CheckOutputModification(
4037 "alternate domain substitutions");
4062 private function FindFile($DirectoryList, $BaseName,
4063 $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
4065 # generate template cache index for this page 4066 $CacheIndex = md5(serialize($DirectoryList))
4067 .
":".self::$ActiveUI.
":".$BaseName;
4069 # if caching is enabled and we have cached location 4070 if (($this->TemplateLocationCacheInterval > 0)
4071 && array_key_exists($CacheIndex,
4072 $this->TemplateLocationCache))
4074 # use template location from cache 4075 $FoundFileName = $this->TemplateLocationCache[$CacheIndex];
4079 # if suffixes specified and base name does not include suffix 4080 if (count($PossibleSuffixes)
4081 && !preg_match(
"/\.[a-zA-Z0-9]+$/", $BaseName))
4083 # add versions of file names with suffixes to file name list 4084 $FileNames = array();
4085 foreach ($PossibleSuffixes as $Suffix)
4087 $FileNames[] = $BaseName.
".".$Suffix;
4092 # use base name as file name 4093 $FileNames = array($BaseName);
4096 # if prefixes specified 4097 if (count($PossiblePrefixes))
4099 # add versions of file names with prefixes to file name list 4100 $NewFileNames = array();
4101 foreach ($FileNames as $FileName)
4103 foreach ($PossiblePrefixes as $Prefix)
4105 $NewFileNames[] = $Prefix.$FileName;
4108 $FileNames = $NewFileNames;
4111 # for each possible location 4112 $FoundFileName = NULL;
4113 foreach ($DirectoryList as $Dir)
4115 # substitute active or default UI name into path 4116 $Dir = str_replace(array(
"%ACTIVEUI%",
"%DEFAULTUI%"),
4117 array(self::$ActiveUI, self::$DefaultUI), $Dir);
4119 # for each possible file name 4120 foreach ($FileNames as $File)
4122 # if template is found at location 4123 if (file_exists($Dir.$File))
4125 # save full template file name and stop looking 4126 $FoundFileName = $Dir.$File;
4132 # save location in cache 4133 $this->TemplateLocationCache[$CacheIndex]
4136 # set flag indicating that cache should be saved 4137 $this->SaveTemplateLocationCache = TRUE;
4140 # return full template file name to caller 4141 return $FoundFileName;
4152 private function CompileScssFile($SrcFile)
4154 # build path to CSS file 4155 $DstFile = self::$ScssCacheDir.
"/".dirname($SrcFile)
4156 .
"/".basename($SrcFile);
4157 $DstFile = substr_replace($DstFile,
"css", -4);
4159 # if SCSS file is newer than CSS file 4160 if (!file_exists($DstFile)
4161 || (filemtime($SrcFile) > filemtime($DstFile)))
4163 # attempt to create CSS cache subdirectory if not present 4164 if (!is_dir(dirname($DstFile)))
4166 @mkdir(dirname($DstFile), 0777, TRUE);
4169 # if CSS cache directory and CSS file path appear writable 4170 static $CacheDirIsWritable;
4171 if (!isset($CacheDirIsWritable))
4172 { $CacheDirIsWritable = is_writable(self::$ScssCacheDir); }
4173 if (is_writable($DstFile)
4174 || (!file_exists($DstFile) && $CacheDirIsWritable))
4176 # load SCSS and compile to CSS 4177 $ScssCode = file_get_contents($SrcFile);
4178 $ScssCompiler =
new scssc();
4179 $ScssCompiler->setFormatter($this->GenerateCompactCss()
4180 ?
"scss_formatter_compressed" :
"scss_formatter");
4183 $CssCode = $ScssCompiler->compile($ScssCode);
4185 # add fingerprinting for URLs in CSS 4186 $this->CssUrlFingerprintPath = dirname($SrcFile);
4187 $CssCode = preg_replace_callback(
4188 "/url\((['\"]*)(.+)\.([a-z]+)(['\"]*)\)/",
4189 array($this,
"CssUrlFingerprintInsertion"),
4192 # strip out comments from CSS (if requested) 4193 if ($this->GenerateCompactCss())
4195 $CssCode = preg_replace(
'!/\*[^*]*\*+([^/][^*]*\*+)*/!',
4199 # write out CSS file 4200 file_put_contents($DstFile, $CssCode);
4202 catch (Exception $Ex)
4204 $this->LogError(self::LOGLVL_ERROR,
4205 "Error compiling SCSS file ".$SrcFile.
": " 4206 .$Ex->getMessage());
4212 # log error and set CSS file path to indicate failure 4213 $this->LogError(self::LOGLVL_ERROR,
4214 "Unable to write out CSS file (compiled from SCSS) to " 4220 # return CSS file path to caller 4231 private function MinimizeJavascriptFile($SrcFile)
4233 # bail out if file is on exclusion list 4234 foreach ($this->DoNotMinimizeList as $DNMFile)
4236 if (($SrcFile == $DNMFile) || (basename($SrcFile) == $DNMFile))
4242 # build path to minimized file 4243 $DstFile = self::$JSMinCacheDir.
"/".dirname($SrcFile)
4244 .
"/".basename($SrcFile);
4245 $DstFile = substr_replace($DstFile,
".min", -3, 0);
4247 # if original file is newer than minimized file 4248 if (!file_exists($DstFile)
4249 || (filemtime($SrcFile) > filemtime($DstFile)))
4251 # attempt to create cache subdirectory if not present 4252 if (!is_dir(dirname($DstFile)))
4254 @mkdir(dirname($DstFile), 0777, TRUE);
4257 # if cache directory and minimized file path appear writable 4258 static $CacheDirIsWritable;
4259 if (!isset($CacheDirIsWritable))
4260 { $CacheDirIsWritable = is_writable(self::$JSMinCacheDir); }
4261 if (is_writable($DstFile)
4262 || (!file_exists($DstFile) && $CacheDirIsWritable))
4264 # load JavaScript code 4265 $Code = file_get_contents($SrcFile);
4267 # decide which minimizer to use 4268 if ($this->JSMinimizerJavaScriptPackerAvailable
4269 && $this->JSMinimizerJShrinkAvailable)
4271 $Minimizer = (strlen($Code) < 5000)
4272 ?
"JShrink" :
"JavaScriptPacker";
4274 elseif ($this->JSMinimizerJShrinkAvailable)
4276 $Minimizer =
"JShrink";
4280 $Minimizer =
"NONE";
4286 case "JavaScriptMinimizer":
4288 $MinimizedCode = $Packer->pack();
4296 catch (Exception $Exception)
4298 unset($MinimizedCode);
4299 $MinimizeError = $Exception->getMessage();
4304 # if minimization succeeded 4305 if (isset($MinimizedCode))
4307 # write out minimized file 4308 file_put_contents($DstFile, $MinimizedCode);
4312 # log error and set destination file path to indicate failure 4313 $ErrMsg =
"Unable to minimize JavaScript file ".$SrcFile;
4314 if (isset($MinimizeError))
4316 $ErrMsg .=
" (".$MinimizeError.
")";
4318 $this->LogError(self::LOGLVL_ERROR, $ErrMsg);
4324 # log error and set destination file path to indicate failure 4325 $this->LogError(self::LOGLVL_ERROR,
4326 "Unable to write out minimized JavaScript to file ".$DstFile);
4331 # return CSS file path to caller 4342 private function CssUrlFingerprintInsertion($Matches)
4344 # generate fingerprint string from CSS file modification time 4345 $FileName = realpath($this->CssUrlFingerprintPath.
"/".
4346 $Matches[2].
".".$Matches[3]);
4347 $MTime = filemtime($FileName);
4348 $Fingerprint = sprintf(
"%06X", ($MTime % 0xFFFFFF));
4350 # build URL string with fingerprint and return it to caller 4351 return "url(".$Matches[1].$Matches[2].
".".$Fingerprint
4352 .
".".$Matches[3].$Matches[4].
")";
4361 private function GetRequiredFilesNotYetLoaded($PageContentFile)
4363 # start out assuming no files required 4364 $RequiredFiles = array();
4366 # if page content file supplied 4367 if ($PageContentFile)
4369 # if file containing list of required files is available 4370 $Path = dirname($PageContentFile);
4371 $RequireListFile = $Path.
"/REQUIRES";
4372 if (file_exists($RequireListFile))
4374 # read in list of required files 4375 $RequestedFiles = file($RequireListFile);
4377 # for each line in required file list 4378 foreach ($RequestedFiles as $Line)
4380 # if line is not a comment 4381 $Line = trim($Line);
4382 if (!preg_match(
"/^#/", $Line))
4384 # if file has not already been loaded 4385 if (!in_array($Line, $this->FoundUIFiles))
4387 # add to list of required files 4388 $RequiredFiles[] = $Line;
4395 # add in additional required files if any 4396 if (count($this->AdditionalRequiredUIFiles))
4398 # make sure there are no duplicates 4399 $AdditionalRequiredUIFiles = array_unique(
4400 $this->AdditionalRequiredUIFiles);
4402 $RequiredFiles = array_merge(
4403 $RequiredFiles, $AdditionalRequiredUIFiles);
4406 # return list of required files to caller 4407 return $RequiredFiles;
4420 private function GetUIFileLoadingTag($FileName, $AdditionalAttributes = NULL)
4422 # pad additional attributes if supplied 4423 $AddAttribs = $AdditionalAttributes ?
" ".$AdditionalAttributes :
"";
4425 # retrieve type of UI file 4426 $FileType = $this->GetFileType($FileName);
4428 # construct tag based on file type 4432 $Tag =
"<link rel=\"stylesheet\" type=\"text/css\"" 4433 .
" media=\"all\" href=\"".$FileName.
"\"" 4437 case self::FT_JAVASCRIPT:
4438 $Tag =
"<script type=\"text/javascript\"" 4439 .
" src=\"".$FileName.
"\"" 4440 .$AddAttribs.
"></script>";
4448 # return constructed tag to caller 4457 public static function AutoloadObjects($ClassName)
4459 # if caching is not turned off 4460 # and we have a cached location for class 4461 # and file at cached location is readable 4462 if ((self::$ObjectLocationCacheInterval > 0)
4463 && array_key_exists($ClassName,
4464 self::$ObjectLocationCache)
4465 && is_readable(self::$ObjectLocationCache[$ClassName]))
4467 # use object location from cache 4468 require_once(self::$ObjectLocationCache[$ClassName]);
4472 # convert any namespace separators in class name 4473 $ClassName = str_replace(
"\\",
"-", $ClassName);
4475 # for each possible object file directory 4477 foreach (self::$ObjectDirectories as $Location => $Info)
4479 # make any needed replacements in directory path 4480 $Location = str_replace(array(
"%ACTIVEUI%",
"%DEFAULTUI%"),
4481 array(self::$ActiveUI, self::$DefaultUI), $Location);
4483 # if directory looks valid 4484 if (is_dir($Location))
4486 # build class file name 4487 $NewClassName = ($Info[
"ClassPattern"] && $Info[
"ClassReplacement"])
4488 ? preg_replace($Info[
"ClassPattern"],
4489 $Info[
"ClassReplacement"], $ClassName)
4492 # read in directory contents if not already retrieved 4493 if (!isset($FileLists[$Location]))
4495 $FileLists[$Location] = self::ReadDirectoryTree(
4496 $Location,
'/^.+\.php$/i');
4499 # for each file in target directory 4500 $FileNames = $FileLists[$Location];
4501 $TargetName = strtolower($Info[
"Prefix"].$NewClassName.
".php");
4502 foreach ($FileNames as $FileName)
4504 # if file matches our target object file name 4505 if (strtolower($FileName) == $TargetName)
4507 # include object file 4508 require_once($Location.$FileName);
4510 # save location to cache 4511 self::$ObjectLocationCache[$ClassName]
4512 = $Location.$FileName;
4514 # set flag indicating that cache should be saved 4515 self::$SaveObjectLocationCache = TRUE;
4534 private static function ReadDirectoryTree($Directory, $Pattern)
4536 $CurrentDir = getcwd();
4538 $DirIter =
new RecursiveDirectoryIterator(
".");
4539 $IterIter =
new RecursiveIteratorIterator($DirIter);
4540 $RegexResults =
new RegexIterator($IterIter, $Pattern,
4541 RecursiveRegexIterator::GET_MATCH);
4542 $FileList = array();
4543 foreach ($RegexResults as $Result)
4545 $FileList[] = substr($Result[0], 2);
4554 private function UndoMagicQuotes()
4556 # if this PHP version has magic quotes support 4557 if (version_compare(PHP_VERSION,
"5.4.0",
"<"))
4559 # turn off runtime magic quotes if on 4560 if (get_magic_quotes_runtime())
4563 set_magic_quotes_runtime(FALSE);
4567 # if magic quotes GPC is on 4568 if (get_magic_quotes_gpc())
4570 # strip added slashes from incoming variables 4571 $GPC = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
4572 array_walk_recursive($GPC,
4573 array($this,
"UndoMagicQuotes_StripCallback"));
4581 private function UndoMagicQuotes_StripCallback(&$Value)
4583 $Value = stripslashes($Value);
4590 private function LoadUIFunctions()
4593 "local/interface/%ACTIVEUI%/include",
4594 "interface/%ACTIVEUI%/include",
4595 "local/interface/%DEFAULTUI%/include",
4596 "interface/%DEFAULTUI%/include",
4598 foreach ($Dirs as $Dir)
4600 $Dir = str_replace(array(
"%ACTIVEUI%",
"%DEFAULTUI%"),
4601 array(self::$ActiveUI, self::$DefaultUI), $Dir);
4604 $FileNames = scandir($Dir);
4605 foreach ($FileNames as $FileName)
4607 if (preg_match(
"/^F-([A-Za-z0-9_]+)\.php/",
4608 $FileName, $Matches)
4609 || preg_match(
"/^F-([A-Za-z0-9_]+)\.html/",
4610 $FileName, $Matches))
4612 if (!function_exists($Matches[1]))
4614 include_once($Dir.
"/".$FileName);
4627 private function ProcessPeriodicEvent($EventName, $Callback)
4629 # retrieve last execution time for event if available 4630 $Signature = self::GetCallbackSignature($Callback);
4631 $LastRun = $this->DB->Query(
"SELECT LastRunAt FROM PeriodicEvents" 4632 .
" WHERE Signature = '".addslashes($Signature).
"'",
"LastRunAt");
4634 # determine whether enough time has passed for event to execute 4635 $ShouldExecute = (($LastRun === NULL)
4636 || (time() > (strtotime($LastRun) + $this->EventPeriods[$EventName])))
4639 # if event should run 4642 # add event to task queue 4643 $WrapperCallback = array(
"ApplicationFramework",
"PeriodicEventWrapper");
4644 $WrapperParameters = array(
4645 $EventName, $Callback, array(
"LastRunAt" => $LastRun));
4646 $this->QueueUniqueTask($WrapperCallback, $WrapperParameters);
4649 # add event to list of periodic events 4650 $this->KnownPeriodicEvents[$Signature] = array(
4651 "Period" => $EventName,
4652 "Callback" => $Callback,
4653 "Queued" => $ShouldExecute);
4663 private static function PeriodicEventWrapper($EventName, $Callback, $Parameters)
4666 if (!isset($DB)) { $DB =
new Database(); }
4669 $ReturnVal = call_user_func_array($Callback, $Parameters);
4671 # if event is already in database 4672 $Signature = self::GetCallbackSignature($Callback);
4673 if ($DB->Query(
"SELECT COUNT(*) AS EventCount FROM PeriodicEvents" 4674 .
" WHERE Signature = '".addslashes($Signature).
"'",
"EventCount"))
4676 # update last run time for event 4677 $DB->Query(
"UPDATE PeriodicEvents SET LastRunAt = " 4678 .(($EventName ==
"EVENT_PERIODIC")
4679 ?
"'".date(
"Y-m-d H:i:s", time() + ($ReturnVal * 60)).
"'" 4681 .
" WHERE Signature = '".addslashes($Signature).
"'");
4685 # add last run time for event to database 4686 $DB->Query(
"INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES " 4687 .
"('".addslashes($Signature).
"', " 4688 .(($EventName ==
"EVENT_PERIODIC")
4689 ?
"'".date(
"Y-m-d H:i:s", time() + ($ReturnVal * 60)).
"'" 4699 private static function GetCallbackSignature($Callback)
4701 return !is_array($Callback) ? $Callback
4702 : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
4710 private function PrepForTSR()
4712 # if HTML has been output and it's time to launch another task 4713 # (only TSR if HTML has been output because otherwise browsers 4714 # may misbehave after connection is closed) 4715 if ((PHP_SAPI !=
"cli")
4716 && ($this->JumpToPage || !$this->SuppressHTML)
4717 && (time() > (strtotime($this->Settings[
"LastTaskRunAt"])
4718 + (ini_get(
"max_execution_time")
4719 / $this->Settings[
"MaxTasksRunning"]) + 5))
4720 && $this->GetTaskQueueSize()
4721 && $this->Settings[
"TaskExecutionEnabled"])
4723 # begin buffering output for TSR 4726 # let caller know it is time to launch another task 4731 # let caller know it is not time to launch another task 4740 private function LaunchTSR()
4742 # set headers to close out connection to browser 4745 ignore_user_abort(TRUE);
4746 header(
"Connection: close");
4747 header(
"Content-Length: ".ob_get_length());
4750 # output buffered content 4751 while (ob_get_level()) { ob_end_flush(); }
4754 # write out any outstanding data and end HTTP session 4755 session_write_close();
4757 # set flag indicating that we are now running in background 4758 $this->RunningInBackground = TRUE;
4760 # handle garbage collection for session data 4761 if (isset($this->SessionStorage) &&
4762 (rand()/getrandmax()) <= $this->SessionGcProbability)
4764 # determine when sessions will expire 4765 $ExpiredTime = strtotime(
"-". self::$SessionLifetime.
" seconds");
4767 # iterate over the files in our session storage, deleting the expired 4768 # ones (sess_ is php's session prefix) 4769 foreach (scandir($this->SessionStorage) as $TgtFile)
4771 $FullPath = $this->SessionStorage.
"/".$TgtFile;
4772 if ( is_file($FullPath) &&
4773 preg_match(
"/^sess_/", $TgtFile) &&
4774 filectime($FullPath) < $ExpiredTime)
4781 # if there is still a task in the queue 4782 if ($this->GetTaskQueueSize())
4784 # garbage collect to give as much memory as possible for tasks 4785 if (function_exists(
"gc_collect_cycles")) { gc_collect_cycles(); }
4787 # turn on output buffering to (hopefully) record any crash output 4790 # lock tables and grab last task run time to double check 4791 $this->DB->Query(
"LOCK TABLES ApplicationFrameworkSettings WRITE");
4792 $this->LoadSettings();
4794 # if still time to launch another task 4795 if (time() > (strtotime($this->Settings[
"LastTaskRunAt"])
4796 + (ini_get(
"max_execution_time")
4797 / $this->Settings[
"MaxTasksRunning"]) + 5))
4799 # update the "last run" time and release tables 4800 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings" 4801 .
" SET LastTaskRunAt = '".date(
"Y-m-d H:i:s").
"'");
4802 $this->DB->Query(
"UNLOCK TABLES");
4804 # run tasks while there is a task in the queue 4805 # and enough time and memory left 4809 $this->RunNextTask();
4811 # calculate percentage of memory still available 4812 $PercentFreeMem = (self::GetFreeMemory()
4813 / self::GetPhpMemoryLimit()) * 100;
4815 while ($this->GetTaskQueueSize()
4816 && ($this->GetSecondsBeforeTimeout() > 65)
4817 && ($PercentFreeMem > $this->BackgroundTaskMinFreeMemPercent));
4822 $this->DB->Query(
"UNLOCK TABLES");
4836 private function GetTaskList($DBQuery, $Count, $Offset)
4838 $this->DB->Query($DBQuery.
" LIMIT ".intval($Offset).
",".intval($Count));
4840 while ($Row = $this->DB->FetchRow())
4842 $Tasks[$Row[
"TaskId"]] = $Row;
4843 if ($Row[
"Callback"] ==
4844 serialize(array(
"ApplicationFramework",
"PeriodicEventWrapper")))
4846 $WrappedCallback = unserialize($Row[
"Parameters"]);
4847 $Tasks[$Row[
"TaskId"]][
"Callback"] = $WrappedCallback[1];
4848 $Tasks[$Row[
"TaskId"]][
"Parameters"] = NULL;
4852 $Tasks[$Row[
"TaskId"]][
"Callback"] = unserialize($Row[
"Callback"]);
4853 $Tasks[$Row[
"TaskId"]][
"Parameters"] = unserialize($Row[
"Parameters"]);
4862 private function RunNextTask()
4864 # lock tables to prevent same task from being run by multiple sessions 4865 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
4867 # look for task at head of queue 4868 $this->DB->Query(
"SELECT * FROM TaskQueue ORDER BY Priority, TaskId LIMIT 1");
4869 $Task = $this->DB->FetchRow();
4871 # if there was a task available 4874 # move task from queue to running tasks list 4875 $this->DB->Query(
"INSERT INTO RunningTasks " 4876 .
"(TaskId,Callback,Parameters,Priority,Description) " 4877 .
"SELECT * FROM TaskQueue WHERE TaskId = " 4878 .intval($Task[
"TaskId"]));
4879 $this->DB->Query(
"DELETE FROM TaskQueue WHERE TaskId = " 4880 .intval($Task[
"TaskId"]));
4882 # release table locks to again allow other sessions to run tasks 4883 $this->DB->Query(
"UNLOCK TABLES");
4885 # unpack stored task info 4886 $Callback = unserialize($Task[
"Callback"]);
4887 $Parameters = unserialize($Task[
"Parameters"]);
4889 # attempt to load task callback if not already available 4890 $this->LoadFunction($Callback);
4892 # save amount of free memory for later comparison 4893 $BeforeFreeMem = self::GetFreeMemory();
4896 $this->RunningTask = $Task;
4899 call_user_func_array($Callback, $Parameters);
4903 call_user_func($Callback);
4905 unset($this->RunningTask);
4907 # log if task leaked significant memory 4908 if (function_exists(
"gc_collect_cycles")) { gc_collect_cycles(); }
4909 $AfterFreeMem = self::GetFreeMemory();
4910 $LeakThreshold = self::GetPhpMemoryLimit()
4911 * ($this->BackgroundTaskMemLeakLogThreshold / 100);
4912 if (($BeforeFreeMem - $AfterFreeMem) > $LeakThreshold)
4914 $this->LogError(self::LOGLVL_DEBUG,
"Task " 4915 .self::GetTaskCallbackSynopsis(
4916 $this->GetTask($Task[
"TaskId"])).
" leaked " 4917 .number_format($BeforeFreeMem - $AfterFreeMem).
" bytes.");
4920 # remove task from running tasks list 4921 $this->DB->Query(
"DELETE FROM RunningTasks" 4922 .
" WHERE TaskId = ".intval($Task[
"TaskId"]));
4924 # prune running tasks list if necessary 4925 $RunningTasksCount = $this->DB->Query(
4926 "SELECT COUNT(*) AS TaskCount FROM RunningTasks",
"TaskCount");
4927 if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
4929 $this->DB->Query(
"DELETE FROM RunningTasks ORDER BY StartedAt" 4930 .
" LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
4935 # release table locks to again allow other sessions to run tasks 4936 $this->DB->Query(
"UNLOCK TABLES");
4945 public function OnCrash()
4947 # attempt to remove any memory limits 4948 $FreeMemory = $this->GetFreeMemory();
4949 ini_set(
"memory_limit", -1);
4951 # if there is a background task currently running 4952 if (isset($this->RunningTask))
4954 # add info about current page load 4955 $CrashInfo[
"ElapsedTime"] = $this->GetElapsedExecutionTime();
4956 $CrashInfo[
"FreeMemory"] = $FreeMemory;
4957 $CrashInfo[
"REMOTE_ADDR"] = $_SERVER[
"REMOTE_ADDR"];
4958 $CrashInfo[
"REQUEST_URI"] = $_SERVER[
"REQUEST_URI"];
4959 if (isset($_SERVER[
"REQUEST_TIME"]))
4961 $CrashInfo[
"REQUEST_TIME"] = $_SERVER[
"REQUEST_TIME"];
4963 if (isset($_SERVER[
"REMOTE_HOST"]))
4965 $CrashInfo[
"REMOTE_HOST"] = $_SERVER[
"REMOTE_HOST"];
4968 # add info about error that caused crash (if available) 4969 if (function_exists(
"error_get_last"))
4971 $CrashInfo[
"LastError"] = error_get_last();
4974 # add info about current output buffer contents (if available) 4975 if (ob_get_length() !== FALSE)
4977 $CrashInfo[
"OutputBuffer"] = ob_get_contents();
4980 # if backtrace info is available for the crash 4981 $Backtrace = debug_backtrace();
4982 if (count($Backtrace) > 1)
4984 # discard the current context from the backtrace 4985 array_shift($Backtrace);
4987 # add the backtrace to the crash info 4988 $CrashInfo[
"Backtrace"] = $Backtrace;
4990 # else if saved backtrace info is available 4991 elseif (isset($this->SavedContext))
4993 # add the saved backtrace to the crash info 4994 $CrashInfo[
"Backtrace"] = $this->SavedContext;
4997 # save crash info for currently running task 4999 $DB->Query(
"UPDATE RunningTasks SET CrashInfo = '" 5000 .addslashes(serialize($CrashInfo))
5001 .
"' WHERE TaskId = ".intval($this->RunningTask[
"TaskId"]));
5024 private function AddToDirList($DirList, $Dir, $SearchLast, $SkipSlashCheck)
5026 # convert incoming directory to array of directories (if needed) 5027 $Dirs = is_array($Dir) ? $Dir : array($Dir);
5029 # reverse array so directories are searched in specified order 5030 $Dirs = array_reverse($Dirs);
5032 # for each directory 5033 foreach ($Dirs as $Location)
5035 # make sure directory includes trailing slash 5036 if (!$SkipSlashCheck)
5038 $Location = $Location
5039 .((substr($Location, -1) !=
"/") ?
"/" :
"");
5042 # remove directory from list if already present 5043 if (in_array($Location, $DirList))
5045 $DirList = array_diff(
5046 $DirList, array($Location));
5049 # add directory to list of directories 5052 array_push($DirList, $Location);
5056 array_unshift($DirList, $Location);
5060 # return updated directory list to caller 5071 private function ArrayPermutations(
$Items, $Perms = array())
5075 $Result = array($Perms);
5080 for ($Index = count(
$Items) - 1; $Index >= 0; --$Index)
5084 list($Segment) = array_splice($NewItems, $Index, 1);
5085 array_unshift($NewPerms, $Segment);
5086 $Result = array_merge($Result,
5087 $this->ArrayPermutations($NewItems, $NewPerms));
5099 private function OutputModificationCallbackShell($Matches)
5101 # call previously-stored external function 5102 return call_user_func($this->OutputModificationCallbackInfo[
"Callback"],
5104 $this->OutputModificationCallbackInfo[
"Pattern"],
5105 $this->OutputModificationCallbackInfo[
"Page"],
5106 $this->OutputModificationCallbackInfo[
"SearchPattern"]);
5117 private function CheckOutputModification($Original, $Modified, $ErrorInfo)
5119 # if error was reported by regex engine 5120 if (preg_last_error() !== PREG_NO_ERROR)
5124 "Error reported by regex engine when modifying output." 5125 .
" (".$ErrorInfo.
")");
5127 # use unmodified version of output 5128 $OutputToUse = $Original;
5130 # else if modification reduced output by more than threshold 5131 elseif ((strlen(trim($Modified)) / strlen(trim($Original)))
5132 < self::OUTPUT_MODIFICATION_THRESHOLD)
5136 "Content reduced below acceptable threshold while modifying output." 5137 .
" (".$ErrorInfo.
")");
5139 # use unmodified version of output 5140 $OutputToUse = $Original;
5144 # use modified version of output 5145 $OutputToUse = $Modified;
5148 # return output to use to caller 5149 return $OutputToUse;
5153 const OUTPUT_MODIFICATION_THRESHOLD = 0.10;
5164 private function UpdateSetting(
5165 $FieldName, $NewValue =
DB_NOVALUE, $Persistent = TRUE)
5167 static $LocalSettings;
5172 $LocalSettings[$FieldName] = $this->DB->UpdateValue(
5173 "ApplicationFrameworkSettings",
5174 $FieldName, $NewValue, NULL, $this->Settings);
5178 $LocalSettings[$FieldName] = $NewValue;
5181 elseif (!isset($LocalSettings[$FieldName]))
5183 $LocalSettings[$FieldName] = $this->DB->UpdateValue(
5184 "ApplicationFrameworkSettings",
5185 $FieldName, $NewValue, NULL, $this->Settings);
5187 return $LocalSettings[$FieldName];
5191 private $InterfaceDirList = array(
5192 "local/interface/%ACTIVEUI%/",
5193 "interface/%ACTIVEUI%/",
5194 "local/interface/%DEFAULTUI%/",
5195 "interface/%DEFAULTUI%/",
5201 private $IncludeDirList = array(
5202 "local/interface/%ACTIVEUI%/include/",
5203 "interface/%ACTIVEUI%/include/",
5204 "interface/%ACTIVEUI%/objects/",
5205 "local/interface/%DEFAULTUI%/include/",
5206 "interface/%DEFAULTUI%/include/",
5207 "interface/%DEFAULTUI%/objects/",
5210 private $ImageDirList = array(
5211 "local/interface/%ACTIVEUI%/images/",
5212 "interface/%ACTIVEUI%/images/",
5213 "local/interface/%DEFAULTUI%/images/",
5214 "interface/%DEFAULTUI%/images/",
5217 private $FunctionDirList = array(
5218 "local/interface/%ACTIVEUI%/include/",
5219 "interface/%ACTIVEUI%/include/",
5220 "local/interface/%DEFAULTUI%/include/",
5221 "interface/%DEFAULTUI%/include/",
5226 const NOVALUE =
".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
5229 # ---- Page Caching (Internal Methods) ----------------------------------- 5236 private function CheckForCachedPage($PageName)
5238 # assume no cached page will be found 5241 # if returning a cached page is allowed 5242 if ($this->CacheCurrentPage)
5244 # get fingerprint for requested page 5245 $PageFingerprint = $this->GetPageFingerprint($PageName);
5247 # look for matching page in cache in database 5248 $this->DB->Query(
"SELECT * FROM AF_CachedPages" 5249 .
" WHERE Fingerprint = '".addslashes($PageFingerprint).
"'");
5251 # if matching page found 5252 if ($this->DB->NumRowsSelected())
5254 # if cached page has expired 5255 $Row = $this->DB->FetchRow();
5256 $ExpirationTime = strtotime(
5257 "-".$this->PageCacheExpirationPeriod().
" seconds");
5258 if (strtotime($Row[
"CachedAt"]) < $ExpirationTime)
5260 # clear expired pages from cache 5261 $ExpirationTimestamp = date(
"Y-m-d H:i:s", $ExpirationTime);
5262 $this->DB->Query(
"DELETE CP, CPTI FROM AF_CachedPages CP," 5263 .
" AF_CachedPageTagInts CPTI" 5264 .
" WHERE CP.CachedAt < '".$ExpirationTimestamp.
"'" 5265 .
" AND CPTI.CacheId = CP.CacheId");
5266 $this->DB->Query(
"DELETE FROM AF_CachedPages " 5267 .
" WHERE CachedAt < '".$ExpirationTimestamp.
"'");
5271 # display cached page and exit 5272 $CachedPage = $Row[
"PageContent"];
5277 # return any cached page found to caller 5286 private function UpdatePageCache($PageName, $PageContent)
5288 # if page caching is enabled and current page should be cached 5289 if ($this->PageCacheEnabled()
5290 && $this->CacheCurrentPage
5291 && ($PageName !=
"404"))
5293 # if page content looks invalid 5294 if (strlen(trim(strip_tags($PageContent))) == 0)
5297 $LogMsg =
"Page not cached because content was empty." 5298 .
" (PAGE: ".$PageName.
", URL: ".$this->FullUrl().
")";
5299 $this->LogError(self::LOGLVL_ERROR, $LogMsg);
5303 # save page to cache 5304 $PageFingerprint = $this->GetPageFingerprint($PageName);
5305 $this->DB->Query(
"INSERT INTO AF_CachedPages" 5306 .
" (Fingerprint, PageContent) VALUES" 5307 .
" ('".$this->DB->EscapeString($PageFingerprint).
"', '" 5308 .$this->DB->EscapeString($PageContent).
"')");
5309 $CacheId = $this->DB->LastInsertId();
5311 # for each page cache tag that was added 5312 foreach ($this->PageCacheTags as $Tag => $Pages)
5314 # if current page is in list for tag 5315 if (in_array(
"CURRENT", $Pages) || in_array($PageName, $Pages))
5318 $TagId = $this->GetPageCacheTagId($Tag);
5320 # mark current page as associated with tag 5321 $this->DB->Query(
"INSERT INTO AF_CachedPageTagInts" 5322 .
" (TagId, CacheId) VALUES " 5323 .
" (".intval($TagId).
", ".intval($CacheId).
")");
5335 private function GetPageCacheTagId($Tag)
5337 # if tag is a non-negative integer 5338 if (is_numeric($Tag) && ($Tag > 0) && (intval($Tag) == $Tag))
5341 $Id = self::PAGECACHETAGIDOFFSET + $Tag;
5345 # look up ID in database 5346 $Id = $this->DB->Query(
"SELECT TagId FROM AF_CachedPageTags" 5347 .
" WHERE Tag = '".addslashes($Tag).
"'",
"TagId");
5349 # if ID was not found 5352 # add tag to database 5353 $this->DB->Query(
"INSERT INTO AF_CachedPageTags" 5354 .
" SET Tag = '".addslashes($Tag).
"'");
5355 $Id = $this->DB->LastInsertId();
5359 # return tag ID to caller 5368 private function GetPageFingerprint($PageName)
5370 # only get the environmental fingerprint once so that it is consistent 5371 # between page construction start and end 5372 static $EnvFingerprint;
5373 if (!isset($EnvFingerprint))
5375 $EnvData = json_encode($_GET).json_encode($_POST);
5377 # if alternate domain support is enabled 5378 if ($this->HtaccessSupport() && self::$RootUrlOverride !== NULL)
5380 # and if we were accessed via an alternate domain 5381 $VHost = $_SERVER[
"SERVER_NAME"];
5382 if (isset($this->AlternateDomainPrefixes[$VHost]))
5384 # then add the alternate domain that was used to our 5390 $EnvFingerprint = md5($EnvData);
5394 # build page fingerprint and return it to caller 5395 return $PageName.
"-".$EnvFingerprint;
Abstraction for forum messages and resource comments.
SQL database abstraction object with smart query caching.
static minify($js, $options=array())
Takes a string containing javascript and removes unneeded characters in order to shrink the code with...
SCSS compiler written in PHP.