3 # FILE: ApplicationFramework.php
5 # Part of the ScoutLib application support library
6 # Copyright 2009-2014 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu
14 class ApplicationFramework {
16 # ---- PUBLIC INTERFACE --------------------------------------------------
24 function __construct()
26 # save execution start time
27 $this->ExecutionStartTime = microtime(TRUE);
29 # begin/restore PHP session
30 $SessionDomain = isset($_SERVER[
"SERVER_NAME"]) ? $_SERVER[
"SERVER_NAME"]
31 : isset($_SERVER[
"HTTP_HOST"]) ? $_SERVER[
"HTTP_HOST"]
33 if (is_writable(session_save_path()))
35 $SessionStorage = session_save_path()
36 .
"/".self::$AppName.
"_".md5($SessionDomain.dirname(__FILE__));
37 if (!is_dir($SessionStorage)) { mkdir($SessionStorage, 0700 ); }
38 if (is_writable($SessionStorage))
40 # save params of our session storage as instance variables for later use
41 $this->SessionGcProbability =
42 ini_get(
"session.gc_probability") / ini_get(
"session.gc_divisor");
43 $this->SessionStorage = $SessionStorage;
45 # store our session files in a subdir to avoid
46 # accidentally sharing sessions with other CWIS installs
48 session_save_path($SessionStorage);
50 # set PHP's gc not to run, as it doesn't handle subdirectories anyway
51 # instead, we'll do the cleanup as we run background tasks
52 ini_set(
"session.gc_probability", 0);
55 ini_set(
"session.gc_maxlifetime", self::$SessionLifetime);
56 session_set_cookie_params(
57 self::$SessionLifetime,
"/", $SessionDomain);
60 # set up default object file search locations
61 self::AddObjectDirectory(
"local/objects");
62 self::AddObjectDirectory(
"objects");
63 self::AddObjectDirectory(
"local/interface/%ACTIVEUI%/objects");
64 self::AddObjectDirectory(
"interface/%ACTIVEUI%/objects");
65 self::AddObjectDirectory(
"local/interface/default/objects");
66 self::AddObjectDirectory(
"interface/default/objects");
68 # set up object file autoloader
69 $this->SetUpObjectAutoloading();
71 # set up function to output any buffered text in case of crash
72 register_shutdown_function(array($this,
"OnCrash"));
74 # set up our internal environment
77 # set up our exception handler
78 set_exception_handler(array($this,
"GlobalExceptionHandler"));
80 # perform any work needed to undo PHP magic quotes
81 $this->UndoMagicQuotes();
83 # load our settings from database
84 $this->LoadSettings();
86 # set PHP maximum execution time
87 $this->MaxExecutionTime($this->Settings[
"MaxExecTime"]);
89 # register events we handle internally
90 $this->RegisterEvent($this->PeriodicEvents);
91 $this->RegisterEvent($this->UIEvents);
93 # attempt to create SCSS cache directory if needed and it does not exist
94 if ($this->ScssSupportEnabled() && !is_dir(self::$ScssCacheDir))
95 { @mkdir(self::$ScssCacheDir, 0777, TRUE); }
97 # attempt to create minimized JS cache directory if needed and it does not exist
98 if ($this->UseMinimizedJavascript()
99 && $this->JavascriptMinimizationEnabled()
100 && !is_dir(self::$JSMinCacheDir))
102 @mkdir(self::$JSMinCacheDir, 0777, TRUE);
111 function __destruct()
113 # if template location cache is flagged to be saved
114 if ($this->SaveTemplateLocationCache)
116 # write template location cache out and update cache expiration
117 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
118 .
" SET TemplateLocationCache = '"
119 .addslashes(serialize(
120 $this->TemplateLocationCache)).
"',"
121 .
" TemplateLocationCacheExpiration = '"
123 $this->TemplateLocationCacheExpiration).
"'");
126 # if object location cache is flagged to be saved
127 if (self::$SaveObjectLocationCache)
129 # write object location cache out and update cache expiration
130 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
131 .
" SET ObjectLocationCache = '"
132 .addslashes(serialize(
133 self::$ObjectLocationCache)).
"',"
134 .
" ObjectLocationCacheExpiration = '"
136 self::$ObjectLocationCacheExpiration).
"'");
145 function GlobalExceptionHandler($Exception)
147 # display exception info
148 $Location = $Exception->getFile().
"[".$Exception->getLine().
"]";
149 ?><table width=
"100%" cellpadding=
"5"
150 style=
"border: 2px solid #666666; background: #CCCCCC;
151 font-family: Courier New, Courier, monospace;
152 margin-top: 10px;"><tr><td>
153 <div style=
"color: #666666;">
154 <span style=
"font-size: 150%;">
155 <b>Uncaught Exception</b></span><br />
156 <b>
Message:</b> <i><?
PHP print $Exception->getMessage(); ?></i><br />
157 <b>Location:</b> <i><?
PHP print $Location; ?></i><br />
159 <blockquote><pre><?
PHP print $Exception->getTraceAsString();
160 ?></pre></blockquote>
162 </td></tr></table><?
PHP
164 # log exception if possible
165 $LogMsg =
"Uncaught exception (".$Exception->getMessage().
").";
166 $this->LogError(self::LOGLVL_ERROR, $LogMsg);
184 static function AddObjectDirectory(
185 $Dir, $Prefix =
"", $ClassPattern = NULL, $ClassReplacement = NULL)
187 # make sure directory has trailing slash
188 $Dir = $Dir.((substr($Dir, -1) !=
"/") ?
"/" :
"");
190 # add directory to directory list
191 self::$ObjectDirectories = array_merge(
194 "ClassPattern" => $ClassPattern,
195 "ClassReplacement" => $ClassReplacement,
197 self::$ObjectDirectories);
219 function AddImageDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
221 # add directories to existing image directory list
222 $this->ImageDirList = $this->AddToDirList(
223 $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
246 function AddIncludeDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
248 # add directories to existing image directory list
249 $this->IncludeDirList = $this->AddToDirList(
250 $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
272 function AddInterfaceDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
274 # add directories to existing image directory list
275 $this->InterfaceDirList = $this->AddToDirList(
276 $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
298 function AddFunctionDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
300 # add directories to existing image directory list
301 $this->FunctionDirList = $this->AddToDirList(
302 $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
310 function SetBrowserDetectionFunc($DetectionFunc)
312 $this->BrowserDetectFunc = $DetectionFunc;
321 function AddUnbufferedCallback($Callback, $Parameters=array())
323 if (is_callable($Callback))
325 $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
335 function TemplateLocationCacheExpirationInterval($NewInterval =
DB_NOVALUE)
337 return $this->UpdateSetting(
"TemplateLocationCacheInterval", $NewInterval);
346 function ObjectLocationCacheExpirationInterval($NewInterval =
DB_NOVALUE)
348 return $this->UpdateSetting(
"ObjectLocationCacheInterval", $NewInterval);
357 function UrlFingerprintingEnabled($NewValue =
DB_NOVALUE)
359 return $this->UpdateSetting(
"UrlFingerprintingEnabled", $NewValue);
369 function ScssSupportEnabled($NewValue =
DB_NOVALUE)
371 return $this->UpdateSetting(
"ScssSupportEnabled", $NewValue);
382 function GenerateCompactCss($NewValue =
DB_NOVALUE)
384 return $this->UpdateSetting(
"GenerateCompactCss", $NewValue);
395 function UseMinimizedJavascript($NewValue =
DB_NOVALUE)
397 return $this->UpdateSetting(
"UseMinimizedJavascript", $NewValue);
408 function JavascriptMinimizationEnabled($NewValue =
DB_NOVALUE)
410 return $this->UpdateSetting(
"JavascriptMinimizationEnabled", $NewValue);
426 function RecordContextInCaseOfCrash(
427 $BacktraceOptions = 0, $BacktraceLimit = 0)
429 if (version_compare(PHP_VERSION,
"5.4.0",
">="))
431 $this->SavedContext = debug_backtrace(
432 $BacktraceOptions, $BacktraceLimit);
436 $this->SavedContext = debug_backtrace($BacktraceOptions);
438 array_shift($this->SavedContext);
445 function LoadPage($PageName)
447 # perform any clean URL rewriting
448 $PageName = $this->RewriteCleanUrls($PageName);
450 # sanitize incoming page name and save local copy
451 $PageName = preg_replace(
"/[^a-zA-Z0-9_.-]/",
"", $PageName);
452 $this->PageName = $PageName;
454 # if page caching is turned on
455 if ($this->PageCacheEnabled())
457 # if we have a cached page
458 $CachedPage = $this->CheckForCachedPage($PageName);
459 if ($CachedPage !== NULL)
461 # set header to indicate cache hit was found
462 header(
"X-ScoutAF-Cache: HIT");
464 # display cached page and exit
470 # set header to indicate no cache hit was found
471 header(
"X-ScoutAF-Cache: MISS");
475 # buffer any output from includes or PHP file
478 # include any files needed to set up execution environment
479 foreach ($this->EnvIncludes as $IncludeFile)
481 include($IncludeFile);
485 $this->SignalEvent(
"EVENT_PAGE_LOAD", array(
"PageName" => $PageName));
487 # signal PHP file load
488 $SignalResult = $this->SignalEvent(
"EVENT_PHP_FILE_LOAD", array(
489 "PageName" => $PageName));
491 # if signal handler returned new page name value
492 $NewPageName = $PageName;
493 if (($SignalResult[
"PageName"] != $PageName)
494 && strlen($SignalResult[
"PageName"]))
496 # if new page name value is page file
497 if (file_exists($SignalResult[
"PageName"]))
499 # use new value for PHP file name
500 $PageFile = $SignalResult[
"PageName"];
504 # use new value for page name
505 $NewPageName = $SignalResult[
"PageName"];
508 # update local copy of page name
509 $this->PageName = $NewPageName;
512 # if we do not already have a PHP file
513 if (!isset($PageFile))
515 # look for PHP file for page
516 $OurPageFile =
"pages/".$NewPageName.
".php";
517 $LocalPageFile =
"local/pages/".$NewPageName.
".php";
518 $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
519 : (file_exists($OurPageFile) ? $OurPageFile
520 :
"pages/".$this->DefaultPage.
".php");
526 # save buffered output to be displayed later after HTML file loads
527 $PageOutput = ob_get_contents();
530 # signal PHP file load is complete
532 $Context[
"Variables"] = get_defined_vars();
533 $this->SignalEvent(
"EVENT_PHP_FILE_LOAD_COMPLETE",
534 array(
"PageName" => $PageName,
"Context" => $Context));
535 $PageCompleteOutput = ob_get_contents();
538 # set up for possible TSR (Terminate and Stay Resident :))
539 $ShouldTSR = $this->PrepForTSR();
541 # if PHP file indicated we should autorefresh to somewhere else
542 if ($this->JumpToPage)
544 if (!strlen(trim($PageOutput)))
546 # if client supports HTTP/1.1, use a 303 as it is most accurate
547 if ($_SERVER[
"SERVER_PROTOCOL"] ==
"HTTP/1.1")
549 header(
"HTTP/1.1 303 See Other");
550 header(
"Location: ".$this->JumpToPage);
554 # if the request was an HTTP/1.0 GET or HEAD, then
555 # use a 302 response code.
557 # NB: both RFC 2616 (HTTP/1.1) and RFC1945 (HTTP/1.0)
558 # explicitly prohibit automatic redirection via a 302
559 # if the request was not GET or HEAD.
560 if ($_SERVER[
"SERVER_PROTOCOL"] ==
"HTTP/1.0" &&
561 ($_SERVER[
"REQUEST_METHOD"] ==
"GET" ||
562 $_SERVER[
"REQUEST_METHOD"] ==
"HEAD") )
564 header(
"HTTP/1.0 302 Found");
565 header(
"Location: ".$this->JumpToPage);
568 # otherwise, fall back to a meta refresh
571 print
'<html><head><meta http-equiv="refresh" '
572 .
'content="0; URL='.$this->JumpToPage.
'">'
573 .
'</head><body></body></html>';
578 # else if HTML loading is not suppressed
579 elseif (!$this->SuppressHTML)
581 # set content-type to get rid of diacritic errors
582 header(
"Content-Type: text/html; charset="
583 .$this->HtmlCharset, TRUE);
585 # load common HTML file (defines common functions) if available
586 $CommonHtmlFile = $this->FindFile($this->IncludeDirList,
587 "Common", array(
"tpl",
"html"));
588 if ($CommonHtmlFile) { include($CommonHtmlFile); }
591 $this->LoadUIFunctions();
593 # begin buffering content
596 # signal HTML file load
597 $SignalResult = $this->SignalEvent(
"EVENT_HTML_FILE_LOAD", array(
598 "PageName" => $PageName));
600 # if signal handler returned new page name value
601 $NewPageName = $PageName;
602 $PageContentFile = NULL;
603 if (($SignalResult[
"PageName"] != $PageName)
604 && strlen($SignalResult[
"PageName"]))
606 # if new page name value is HTML file
607 if (file_exists($SignalResult[
"PageName"]))
609 # use new value for HTML file name
610 $PageContentFile = $SignalResult[
"PageName"];
614 # use new value for page name
615 $NewPageName = $SignalResult[
"PageName"];
619 # load page content HTML file if available
620 if ($PageContentFile === NULL)
622 $PageContentFile = $this->FindFile(
623 $this->InterfaceDirList, $NewPageName,
624 array(
"tpl",
"html"));
626 if ($PageContentFile)
628 include($PageContentFile);
632 print
"<h2>ERROR: No HTML/TPL template found"
633 .
" for this page (".$NewPageName.
").</h2>";
636 # signal HTML file load complete
637 $SignalResult = $this->SignalEvent(
"EVENT_HTML_FILE_LOAD_COMPLETE");
639 # stop buffering and save output
640 $PageContentOutput = ob_get_contents();
643 # load page start HTML file if available
645 $PageStartFile = $this->FindFile($this->IncludeDirList,
"Start",
646 array(
"tpl",
"html"), array(
"StdPage",
"StandardPage"));
647 if ($PageStartFile) { include($PageStartFile); }
648 $PageStartOutput = ob_get_contents();
651 # load page end HTML file if available
653 $PageEndFile = $this->FindFile($this->IncludeDirList,
"End",
654 array(
"tpl",
"html"), array(
"StdPage",
"StandardPage"));
655 if ($PageEndFile) { include($PageEndFile); }
656 $PageEndOutput = ob_get_contents();
659 # get list of any required files not loaded
660 $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
662 # if a browser detection function has been made available
663 if (is_callable($this->BrowserDetectFunc))
665 # call function to get browser list
666 $Browsers = call_user_func($this->BrowserDetectFunc);
668 # for each required file
669 $NewRequiredFiles = array();
670 foreach ($RequiredFiles as $File)
672 # if file name includes browser keyword
673 if (preg_match(
"/%BROWSER%/", $File))
676 foreach ($Browsers as $Browser)
678 # substitute in browser name and add to new file list
679 $NewRequiredFiles[] = preg_replace(
680 "/%BROWSER%/", $Browser, $File);
685 # add to new file list
686 $NewRequiredFiles[] = $File;
689 $RequiredFiles = $NewRequiredFiles;
693 # filter out any files with browser keyword in their name
694 $NewRequiredFiles = array();
695 foreach ($RequiredFiles as $File)
697 if (!preg_match(
"/%BROWSER%/", $File))
699 $NewRequiredFiles[] = $File;
702 $RequiredFiles = $NewRequiredFiles;
705 # for each required file
706 foreach ($RequiredFiles as $File)
708 # locate specific file to use
709 $FilePath = $this->GUIFile($File);
714 # determine file type
715 $NamePieces = explode(
".", $File);
716 $FileSuffix = strtolower(array_pop($NamePieces));
718 # add file to HTML output based on file type
719 $FilePath = htmlspecialchars($FilePath);
723 $Tag =
'<script type="text/javascript" src="'
724 .$FilePath.
'"></script>';
725 $PageEndOutput = preg_replace(
726 "#</body>#i", $Tag.
"\n</body>", $PageEndOutput, 1);
730 $Tag =
'<link rel="stylesheet" type="text/css"'
731 .
' media="all" href="'.$FilePath.
'">';
732 $PageStartOutput = preg_replace(
733 "#</head>#i", $Tag.
"\n</head>", $PageStartOutput, 1);
740 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
742 # perform any regular expression replacements in output
743 $FullPageOutput = preg_replace($this->OutputModificationPatterns,
744 $this->OutputModificationReplacements, $FullPageOutput);
746 # perform any callback replacements in output
747 foreach ($this->OutputModificationCallbacks as $Info)
749 $this->OutputModificationCallbackInfo = $Info;
750 $FullPageOutput = preg_replace_callback($Info[
"SearchPattern"],
751 array($this,
"OutputModificationCallbackShell"),
755 # provide the opportunity to modify full page output
756 $SignalResult = $this->SignalEvent(
"EVENT_PAGE_OUTPUT_FILTER", array(
757 "PageOutput" => $FullPageOutput));
758 if (isset($SignalResult[
"PageOutput"])
759 && strlen($SignalResult[
"PageOutput"]))
761 $FullPageOutput = $SignalResult[
"PageOutput"];
764 # if relative paths may not work because we were invoked via clean URL
765 if ($this->CleanUrlRewritePerformed || self::WasUrlRewritten())
767 # if using the <base> tag is okay
768 $BaseUrl = $this->BaseUrl();
769 if ($this->UseBaseTag)
771 # add <base> tag to header
772 $PageStartOutput = preg_replace(
"%<head>%",
773 "<head><base href=\"".$BaseUrl.
"\" />",
776 # re-assemble full page with new header
777 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
779 # the absolute URL to the current page
780 $FullUrl = $BaseUrl . $this->GetPageLocation();
782 # make HREF attribute values with just a fragment ID
783 # absolute since they don't work with the <base> tag because
784 # they are relative to the current page/URL, not the site
786 $FullPageOutput = preg_replace(
787 array(
"%href=\"(#[^:\" ]+)\"%i",
"%href='(#[^:' ]+)'%i"),
788 array(
"href=\"".$FullUrl.
"$1\"",
"href='".$FullUrl.
"$1'"),
793 # try to fix any relative paths throughout code
794 $FullPageOutput = preg_replace(array(
795 "%src=\"/?([^?*:;{}\\\\\" ]+)\.(js|css|gif|png|jpg)\"%i",
796 "%src='/?([^?*:;{}\\\\' ]+)\.(js|css|gif|png|jpg)'%i",
797 # don
't rewrite HREF attributes that are just
798 # fragment IDs because they are relative to the
799 # current page/URL, not the site root
800 "%href=\"/?([^#][^:\" ]*)\"%i",
801 "%href='/?([^#][^:
' ]*)'%i
",
802 "%action=\
"/?([^#][^:\" ]*)\"%i",
803 "%action='/?([^#][^:' ]*)'%i",
804 "%@import\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
805 "%@import\s+url\('/?([^:\" ]+)'\s*\)%i",
806 "%src:\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
807 "%src:\s+url\('/?([^:\" ]+)'\s*\)%i",
808 "%@import\s+\"/?([^:\" ]+)\"\s*%i",
809 "%@import\s+'/?([^:\" ]+)'\s*%i",
812 "src=\"".$BaseUrl.
"$1.$2\"",
813 "src=\"".$BaseUrl.
"$1.$2\"",
814 "href=\"".$BaseUrl.
"$1\"",
815 "href=\"".$BaseUrl.
"$1\"",
816 "action=\"".$BaseUrl.
"$1\"",
817 "action=\"".$BaseUrl.
"$1\"",
818 "@import url(\"".$BaseUrl.
"$1\")",
819 "@import url('".$BaseUrl.
"$1')",
820 "src: url(\"".$BaseUrl.
"$1\")",
821 "src: url('".$BaseUrl.
"$1')",
822 "@import \"".$BaseUrl.
"$1\"",
823 "@import '".$BaseUrl.
"$1'",
829 # write out full page
830 print $FullPageOutput;
832 # update page cache for this page
833 $this->UpdatePageCache($PageName, $FullPageOutput);
836 # run any post-processing routines
837 foreach ($this->PostProcessingFuncs as $Func)
839 call_user_func_array($Func[
"FunctionName"], $Func[
"Arguments"]);
842 # write out any output buffered from page code execution
843 if (strlen($PageOutput))
845 if (!$this->SuppressHTML)
847 ?><table width=
"100%" cellpadding=
"5"
848 style=
"border: 2px solid #666666; background: #CCCCCC;
849 font-family: Courier New, Courier, monospace;
850 margin-top: 10px;"><tr><td><?
PHP
852 if ($this->JumpToPage)
854 ?><div style=
"color: #666666;"><span style=
"font-size: 150%;">
855 <b>Page Jump Aborted</b></span>
856 (because of error or other unexpected output)<br />
858 <i><?
PHP print($this->JumpToPage); ?></i></div><?
PHP
861 if (!$this->SuppressHTML)
863 ?></td></tr></table><?
PHP
867 # write out any output buffered from the page code execution complete signal
868 if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
870 print $PageCompleteOutput;
873 # execute callbacks that should not have their output buffered
874 foreach ($this->UnbufferedCallbacks as $Callback)
876 call_user_func_array($Callback[0], $Callback[1]);
879 # log high memory usage
880 if (function_exists(
"memory_get_peak_usage"))
882 $MemoryThreshold = ($this->HighMemoryUsageThreshold()
883 * $this->GetPhpMemoryLimit()) / 100;
884 if ($this->LogHighMemoryUsage()
885 && (memory_get_peak_usage() >= $MemoryThreshold))
887 $HighMemUsageMsg =
"High peak memory usage ("
888 .intval(memory_get_peak_usage()).
") for "
889 .$this->FullUrl().
" from "
890 .$_SERVER[
"REMOTE_ADDR"];
891 $this->LogMessage(self::LOGLVL_INFO, $HighMemUsageMsg);
895 # log slow page loads
896 if ($this->LogSlowPageLoads()
897 && ($this->GetElapsedExecutionTime()
898 >= ($this->SlowPageLoadThreshold())))
900 $SlowPageLoadMsg =
"Slow page load ("
901 .intval($this->GetElapsedExecutionTime()).
"s) for "
902 .$this->FullUrl().
" from "
903 .$_SERVER[
"REMOTE_ADDR"];
904 $this->LogMessage(self::LOGLVL_INFO, $SlowPageLoadMsg);
907 # terminate and stay resident (TSR!) if indicated and HTML has been output
908 # (only TSR if HTML has been output because otherwise browsers will misbehave)
909 if ($ShouldTSR) { $this->LaunchTSR(); }
917 function GetPageName()
919 return $this->PageName;
927 function GetPageLocation()
929 # retrieve current URL
930 $Url = $this->GetScriptUrl();
932 # remove the base path if present
933 $BasePath = $this->Settings[
"BasePath"];
934 if (stripos($Url, $BasePath) === 0)
936 $Url = substr($Url, strlen($BasePath));
947 function GetPageUrl()
949 return self::BaseUrl() . $this->GetPageLocation();
960 function SetJumpToPage($Page, $IsLiteral = FALSE)
964 && (strpos($Page,
"?") === FALSE)
965 && ((strpos($Page,
"=") !== FALSE)
966 || ((stripos($Page,
".php") === FALSE)
967 && (stripos($Page,
".htm") === FALSE)
968 && (strpos($Page,
"/") === FALSE)))
969 && (stripos($Page,
"http://") !== 0)
970 && (stripos($Page,
"https://") !== 0))
972 $this->JumpToPage = self::BaseUrl() .
"index.php?P=".$Page;
976 $this->JumpToPage = $Page;
984 function JumpToPageIsSet()
986 return ($this->JumpToPage === NULL) ? FALSE : TRUE;
998 function HtmlCharset($NewSetting = NULL)
1000 if ($NewSetting !== NULL) { $this->HtmlCharset = $NewSetting; }
1001 return $this->HtmlCharset;
1013 function DoNotMinimizeFile($File)
1015 if (!is_array($File)) { $File = array($File); }
1016 $this->DoNotMinimizeList = array_merge($this->DoNotMinimizeList, $File);
1029 function UseBaseTag($NewValue = NULL)
1031 if ($NewValue !== NULL) { $this->UseBaseTag = $NewValue ? TRUE : FALSE; }
1032 return $this->UseBaseTag;
1041 function SuppressHTMLOutput($NewSetting = TRUE)
1043 $this->SuppressHTML = $NewSetting;
1052 static function ActiveUserInterface($UIName = NULL)
1054 if ($UIName !== NULL)
1056 self::$ActiveUI = preg_replace(
"/^SPTUI--/",
"", $UIName);
1058 return self::$ActiveUI;
1066 function GetUserInterfaces()
1068 # possible UI directories
1069 $InterfaceDirs = array(
1073 # start out with an empty list
1074 $Interfaces = array();
1076 # for each possible UI directory
1077 foreach ($InterfaceDirs as $InterfaceDir)
1079 $Dir = dir($InterfaceDir);
1081 # for each file in current directory
1082 while (($DirEntry = $Dir->read()) !== FALSE)
1084 $InterfacePath = $InterfaceDir.
"/".$DirEntry;
1086 # skip anything that doesn't have a name in the required format
1087 if (!preg_match(
'/^[a-zA-Z0-9]+$/', $DirEntry))
1092 # skip anything that isn't a directory
1093 if (!is_dir($InterfacePath))
1098 # read the UI name (if available)
1099 $UIName = @file_get_contents($InterfacePath.
"/NAME");
1101 # use the directory name if the UI name isn't available
1102 if ($UIName === FALSE || !strlen($UIName))
1104 $UIName = $DirEntry;
1107 $Interfaces[$InterfacePath] = $UIName;
1113 # return list to caller
1141 function AddPostProcessingCall($FunctionName,
1142 &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
1143 &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
1144 &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
1146 $FuncIndex = count($this->PostProcessingFuncs);
1147 $this->PostProcessingFuncs[$FuncIndex][
"FunctionName"] = $FunctionName;
1148 $this->PostProcessingFuncs[$FuncIndex][
"Arguments"] = array();
1150 while (isset(${
"Arg".$Index}) && (${
"Arg".$Index} !== self::NOVALUE))
1152 $this->PostProcessingFuncs[$FuncIndex][
"Arguments"][$Index]
1163 function AddEnvInclude($FileName)
1165 $this->EnvIncludes[] = $FileName;
1174 function GUIFile($FileName)
1176 # determine which location to search based on file suffix
1177 $FileType = $this->GetFileType($FileName);
1178 $DirList = ($FileType == self::FT_IMAGE)
1179 ? $this->ImageDirList : $this->IncludeDirList;
1181 # if directed to use minimized JavaScript file
1182 if (($FileType == self::FT_JAVASCRIPT) && $this->UseMinimizedJavascript())
1184 # look for minimized version of file
1185 $MinimizedFileName = substr_replace($FileName,
".min", -3, 0);
1186 $FoundFileName = $this->FindFile($DirList, $MinimizedFileName);
1188 # if minimized file was not found
1189 if (is_null($FoundFileName))
1191 # look for unminimized file
1192 $FoundFileName = $this->FindFile($DirList, $FileName);
1194 # if unminimized file found
1195 if (!is_null($FoundFileName))
1197 # if minimization enabled and supported
1198 if ($this->JavascriptMinimizationEnabled()
1199 && isset($_SERVER[
"JSMIN_REWRITE_SUPPORT"]))
1201 # attempt to create minimized file
1202 $MinFileName = $this->MinimizeJavascriptFile(
1205 # if minimization succeeded
1206 if ($MinFileName !== NULL)
1208 # use minimized version
1209 $FoundFileName = $MinFileName;
1211 # save file modification time if needed for fingerprinting
1212 if ($this->UrlFingerprintingEnabled())
1214 $FileMTime = filemtime($FoundFileName);
1217 # strip off the cache location, allowing .htaccess
1218 # to handle that for us
1219 $FoundFileName = str_replace(
1220 self::$JSMinCacheDir.
"/",
"", $FoundFileName);
1226 # else if directed to use SCSS files
1227 elseif (($FileType == self::FT_CSS) && $this->ScssSupportEnabled())
1229 # look for SCSS version of file
1230 $SourceFileName = preg_replace(
"/.css$/",
".scss", $FileName);
1231 $FoundSourceFileName = $this->FindFile($DirList, $SourceFileName);
1233 # if SCSS file not found
1234 if ($FoundSourceFileName === NULL)
1237 $FoundFileName = $this->FindFile($DirList, $FileName);
1241 # compile SCSS file (if updated) and return resulting CSS file
1242 $FoundFileName = $this->CompileScssFile($FoundSourceFileName);
1244 # save file modification time if needed for fingerprinting
1245 if ($this->UrlFingerprintingEnabled())
1247 $FileMTime = filemtime($FoundFileName);
1250 # strip off the cache location, allowing .htaccess to handle that for us
1251 if (isset($_SERVER[
"SCSS_REWRITE_SUPPORT"]))
1253 $FoundFileName = str_replace(
1254 self::$ScssCacheDir.
"/",
"", $FoundFileName);
1258 # otherwise just search for the file
1261 $FoundFileName = $this->FindFile($DirList, $FileName);
1264 # add non-image files to list of found files (used for required files loading)
1265 if ($FileType != self::FT_IMAGE)
1266 { $this->FoundUIFiles[] = basename($FoundFileName); }
1268 # if UI file fingerprinting is enabled and supported
1269 if ($this->UrlFingerprintingEnabled()
1270 && array_key_exists(
"URL_FINGERPRINTING_SUPPORT", $_SERVER)
1271 && $_SERVER[
"URL_FINGERPRINTING_SUPPORT"]
1272 && (isset($FileMTime) || file_exists($FoundFileName)))
1274 # if file does not appear to be a server-side inclusion
1275 if (!preg_match(
'/\.(html|php)$/i', $FoundFileName))
1277 # for each URL fingerprinting blacklist entry
1278 $OnBlacklist = FALSE;
1279 foreach ($this->UrlFingerprintBlacklist as $BlacklistEntry)
1281 # if entry looks like a regular expression pattern
1282 if ($BlacklistEntry[0] == substr($BlacklistEntry, -1))
1284 # check file name against regular expression
1285 if (preg_match($BlacklistEntry, $FoundFileName))
1287 $OnBlacklist = TRUE;
1293 # check file name directly against entry
1294 if (basename($FoundFileName) == $BlacklistEntry)
1296 $OnBlacklist = TRUE;
1302 # if file was not on blacklist
1305 # get file modification time if not already retrieved
1306 if (!isset($FileMTime))
1308 $FileMTime = filemtime($FoundFileName);
1311 # add timestamp fingerprint to file name
1312 $Fingerprint = sprintf(
"%06X",
1313 ($FileMTime % 0xFFFFFF));
1314 $FoundFileName = preg_replace(
"/^(.+)\.([a-z]+)$/",
1315 "$1.".$Fingerprint.
".$2",
1321 # return file name to caller
1322 return $FoundFileName;
1333 function PUIFile($FileName)
1335 $FullFileName = $this->GUIFile($FileName);
1336 if ($FullFileName) { print($FullFileName); }
1353 function IncludeUIFile($FileNames, $AdditionalAttributes = NULL)
1355 # convert file name to array if necessary
1356 if (!is_array($FileNames)) { $FileNames = array($FileNames); }
1358 # pad additional attributes if supplied
1359 $AddAttribs = $AdditionalAttributes ?
" ".$AdditionalAttributes :
"";
1362 foreach ($FileNames as $BaseFileName)
1364 # retrieve full file name
1365 $FileName = $this->GUIFile($BaseFileName);
1370 # print appropriate tag based on file type
1371 $FileType = $this->GetFileType($FileName);
1375 print
"<link rel=\"stylesheet\" type=\"text/css\""
1376 .
" media=\"all\" href=\"".$FileName.
"\""
1377 .$AdditionalAttributes.
" />";
1378 $OverrideFileName = preg_replace(
1379 "/\.(css|scss)$/",
"-Override.$1",
1381 $this->IncludeUIFile($OverrideFileName,
1382 $AdditionalAttributes);
1385 case self::FT_JAVASCRIPT:
1386 print
"<script type=\"text/javascript\""
1387 .
" src=\"".$FileName.
"\""
1388 .$AddAttribs.
"></script>";
1389 $OverrideFileName = preg_replace(
1390 "/\.js$/",
"-Override.js",
1392 $this->IncludeUIFile($OverrideFileName,
1393 $AdditionalAttributes);
1396 case self::FT_IMAGE:
1409 function DoNotUrlFingerprint($Pattern)
1411 $this->UrlFingerprintBlacklist[] = $Pattern;
1421 function RequireUIFile($FileName)
1423 $this->AdditionalRequiredUIFiles[] = $FileName;
1431 static function GetFileType($FileName)
1433 $FileSuffix = strtolower(substr($FileName, -3));
1434 if ($FileSuffix ==
"css") {
return self::FT_CSS; }
1435 elseif ($FileSuffix ==
".js") {
return self::FT_JAVASCRIPT; }
1436 elseif (($FileSuffix ==
"gif")
1437 || ($FileSuffix ==
"jpg")
1438 || ($FileSuffix ==
"png")) {
return self::FT_IMAGE; }
1439 return self::FT_OTHER;
1448 const FT_JAVASCRIPT = 3;
1458 function LoadFunction($Callback)
1460 # if specified function is not currently available
1461 if (!is_callable($Callback))
1463 # if function info looks legal
1464 if (is_string($Callback) && strlen($Callback))
1466 # start with function directory list
1467 $Locations = $this->FunctionDirList;
1469 # add object directories to list
1470 $Locations = array_merge(
1471 $Locations, array_keys(self::$ObjectDirectories));
1473 # look for function file
1474 $FunctionFileName = $this->FindFile($Locations,
"F-".$Callback,
1475 array(
"php",
"html"));
1477 # if function file was found
1478 if ($FunctionFileName)
1480 # load function file
1481 include_once($FunctionFileName);
1485 # log error indicating function load failed
1486 $this->LogError(self::LOGLVL_ERROR,
"Unable to load function"
1487 .
" for callback \"".$Callback.
"\".");
1492 # log error indicating specified function info was bad
1493 $this->LogError(self::LOGLVL_ERROR,
"Unloadable callback value"
1495 .
" passed to AF::LoadFunction().");
1499 # report to caller whether function load succeeded
1500 return is_callable($Callback);
1507 function GetElapsedExecutionTime()
1509 return microtime(TRUE) - $this->ExecutionStartTime;
1516 function GetSecondsBeforeTimeout()
1518 return ini_get(
"max_execution_time") - $this->GetElapsedExecutionTime();
1524 # ---- Page Caching ------------------------------------------------------
1534 function PageCacheEnabled($NewValue =
DB_NOVALUE)
1536 return $this->UpdateSetting(
"PageCacheEnabled", $NewValue);
1545 function PageCacheExpirationPeriod($NewValue =
DB_NOVALUE)
1547 return $this->UpdateSetting(
"PageCacheExpirationPeriod", $NewValue);
1554 function DoNotCacheCurrentPage()
1556 $this->CacheCurrentPage = FALSE;
1565 function AddPageCacheTag($Tag, $Pages = NULL)
1568 $Tag = strtolower($Tag);
1570 # if pages were supplied
1571 if ($Pages !== NULL)
1573 # add pages to list for this tag
1574 if (isset($this->PageCacheTags[$Tag]))
1576 $this->PageCacheTags[$Tag] = array_merge(
1577 $this->PageCacheTags[$Tag], $Pages);
1581 $this->PageCacheTags[$Tag] = $Pages;
1586 # add current page to list for this tag
1587 $this->PageCacheTags[$Tag][] =
"CURRENT";
1596 function ClearPageCacheForTag($Tag)
1599 $TagId = $this->GetPageCacheTagId($Tag);
1601 # delete pages and tag/page connections for specified tag
1602 $this->DB->Query(
"DELETE CP, CPTI"
1603 .
" FROM AF_CachedPages CP, AF_CachedPageTagInts CPTI"
1604 .
" WHERE CPTI.TagId = ".intval($TagId)
1605 .
" AND CP.CacheId = CPTI.CacheId");
1611 function ClearPageCache()
1613 # clear all page cache tables
1614 $this->DB->Query(
"TRUNCATE TABLE AF_CachedPages");
1615 $this->DB->Query(
"TRUNCATE TABLE AF_CachedPageTags");
1616 $this->DB->Query(
"TRUNCATE TABLE AF_CachedPageTagInts");
1625 function GetPageCacheInfo()
1627 $Length = $this->DB->Query(
"SELECT COUNT(*) AS CacheLen"
1628 .
" FROM AF_CachedPages",
"CacheLen");
1629 $Oldest = $this->DB->Query(
"SELECT CachedAt FROM AF_CachedPages"
1630 .
" ORDER BY CachedAt ASC LIMIT 1",
"CachedAt");
1632 "NumberOfEntries" => $Length,
1633 "OldestTimestamp" => strtotime($Oldest),
1640 # ---- Logging -----------------------------------------------------------
1654 function LogSlowPageLoads($NewValue =
DB_NOVALUE)
1656 return $this->UpdateSetting(
"LogSlowPageLoads", $NewValue);
1666 function SlowPageLoadThreshold($NewValue =
DB_NOVALUE)
1668 return $this->UpdateSetting(
"SlowPageLoadThreshold", $NewValue);
1681 function LogHighMemoryUsage($NewValue =
DB_NOVALUE)
1683 return $this->UpdateSetting(
"LogHighMemoryUsage", $NewValue);
1694 function HighMemoryUsageThreshold($NewValue =
DB_NOVALUE)
1696 return $this->UpdateSetting(
"HighMemoryUsageThreshold", $NewValue);
1712 function LogError($Level, $Msg)
1714 # if error level is at or below current logging level
1715 if ($this->Settings[
"LoggingLevel"] >= $Level)
1717 # attempt to log error message
1718 $Result = $this->LogMessage($Level, $Msg);
1720 # if logging attempt failed and level indicated significant error
1721 if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
1723 # throw exception about inability to log error
1724 static $AlreadyThrewException = FALSE;
1725 if (!$AlreadyThrewException)
1727 $AlreadyThrewException = TRUE;
1728 throw new Exception(
"Unable to log error (".$Level.
": ".$Msg
1729 .
") to ".$this->LogFileName);
1733 # report to caller whether message was logged
1738 # report to caller that message was not logged
1754 function LogMessage($Level, $Msg)
1756 # if message level is at or below current logging level
1757 if ($this->Settings[
"LoggingLevel"] >= $Level)
1759 # attempt to open log file
1760 $FHndl = @fopen($this->LogFileName,
"a");
1762 # if log file could not be open
1763 if ($FHndl === FALSE)
1765 # report to caller that message was not logged
1771 $ErrorAbbrevs = array(
1772 self::LOGLVL_FATAL =>
"FTL",
1773 self::LOGLVL_ERROR =>
"ERR",
1774 self::LOGLVL_WARNING =>
"WRN",
1775 self::LOGLVL_INFO =>
"INF",
1776 self::LOGLVL_DEBUG =>
"DBG",
1777 self::LOGLVL_TRACE =>
"TRC",
1779 $LogEntry = date(
"Y-m-d H:i:s")
1780 .
" ".($this->RunningInBackground ?
"B" :
"F")
1781 .
" ".$ErrorAbbrevs[$Level]
1784 # write entry to log
1785 $Success = fwrite($FHndl, $LogEntry.
"\n");
1790 # report to caller whether message was logged
1791 return ($Success === FALSE) ? FALSE : TRUE;
1796 # report to caller that message was not logged
1822 function LoggingLevel($NewValue =
DB_NOVALUE)
1824 # constrain new level (if supplied) to within legal bounds
1827 $NewValue = max(min($NewValue, 6), 1);
1830 # set new logging level (if supplied) and return current level to caller
1831 return $this->UpdateSetting(
"LoggingLevel", $NewValue);
1840 function LogFile($NewValue = NULL)
1842 if ($NewValue !== NULL) { $this->LogFileName = $NewValue; }
1843 return $this->LogFileName;
1850 const LOGLVL_TRACE = 6;
1855 const LOGLVL_DEBUG = 5;
1861 const LOGLVL_INFO = 4;
1866 const LOGLVL_WARNING = 3;
1872 const LOGLVL_ERROR = 2;
1877 const LOGLVL_FATAL = 1;
1882 # ---- Event Handling ----------------------------------------------------
1889 const EVENTTYPE_DEFAULT = 1;
1895 const EVENTTYPE_CHAIN = 2;
1901 const EVENTTYPE_FIRST = 3;
1909 const EVENTTYPE_NAMED = 4;
1912 const ORDER_FIRST = 1;
1914 const ORDER_MIDDLE = 2;
1916 const ORDER_LAST = 3;
1926 function RegisterEvent($EventsOrEventName, $EventType = NULL)
1928 # convert parameters to array if not already in that form
1929 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1930 : array($EventsOrEventName => $EventType);
1933 foreach ($Events as $Name => $Type)
1935 # store event information
1936 $this->RegisteredEvents[$Name][
"Type"] = $Type;
1937 $this->RegisteredEvents[$Name][
"Hooks"] = array();
1947 function IsRegisteredEvent($EventName)
1949 return array_key_exists($EventName, $this->RegisteredEvents)
1959 function IsHookedEvent($EventName)
1961 # the event isn't hooked to if it isn't even registered
1962 if (!$this->IsRegisteredEvent($EventName))
1967 # return TRUE if there is at least one callback hooked to the event
1968 return count($this->RegisteredEvents[$EventName][
"Hooks"]) > 0;
1984 function HookEvent($EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
1986 # convert parameters to array if not already in that form
1987 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1988 : array($EventsOrEventName => $Callback);
1992 foreach ($Events as $EventName => $EventCallback)
1994 # if callback is valid
1995 if (is_callable($EventCallback))
1997 # if this is a periodic event we process internally
1998 if (isset($this->PeriodicEvents[$EventName]))
2001 $this->ProcessPeriodicEvent($EventName, $EventCallback);
2003 # if specified event has been registered
2004 elseif (isset($this->RegisteredEvents[$EventName]))
2006 # add callback for event
2007 $this->RegisteredEvents[$EventName][
"Hooks"][]
2008 = array(
"Callback" => $EventCallback,
"Order" => $Order);
2010 # sort callbacks by order
2011 if (count($this->RegisteredEvents[$EventName][
"Hooks"]) > 1)
2013 usort($this->RegisteredEvents[$EventName][
"Hooks"],
2014 array(
"ApplicationFramework",
"HookEvent_OrderCompare"));
2028 # report to caller whether all callbacks were hooked
2032 private static function HookEvent_OrderCompare($A, $B)
2034 if ($A[
"Order"] == $B[
"Order"]) {
return 0; }
2035 return ($A[
"Order"] < $B[
"Order"]) ? -1 : 1;
2048 function SignalEvent($EventName, $Parameters = NULL)
2050 $ReturnValue = NULL;
2052 # if event has been registered
2053 if (isset($this->RegisteredEvents[$EventName]))
2055 # set up default return value (if not NULL)
2056 switch ($this->RegisteredEvents[$EventName][
"Type"])
2058 case self::EVENTTYPE_CHAIN:
2059 $ReturnValue = $Parameters;
2062 case self::EVENTTYPE_NAMED:
2063 $ReturnValue = array();
2067 # for each callback for this event
2068 foreach ($this->RegisteredEvents[$EventName][
"Hooks"] as $Hook)
2071 $Callback = $Hook[
"Callback"];
2072 $Result = ($Parameters !== NULL)
2073 ? call_user_func_array($Callback, $Parameters)
2074 : call_user_func($Callback);
2076 # process return value based on event type
2077 switch ($this->RegisteredEvents[$EventName][
"Type"])
2079 case self::EVENTTYPE_CHAIN:
2080 if ($Result !== NULL)
2082 foreach ($Parameters as $Index => $Value)
2084 if (array_key_exists($Index, $Result))
2086 $Parameters[$Index] = $Result[$Index];
2089 $ReturnValue = $Parameters;
2093 case self::EVENTTYPE_FIRST:
2094 if ($Result !== NULL)
2096 $ReturnValue = $Result;
2101 case self::EVENTTYPE_NAMED:
2102 $CallbackName = is_array($Callback)
2103 ? (is_object($Callback[0])
2104 ? get_class($Callback[0])
2105 : $Callback[0]).
"::".$Callback[1]
2107 $ReturnValue[$CallbackName] = $Result;
2117 $this->LogError(self::LOGLVL_WARNING,
2118 "Unregistered event signaled (".$EventName.
").");
2121 # return value if any to caller
2122 return $ReturnValue;
2130 function IsStaticOnlyEvent($EventName)
2132 return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
2145 function EventWillNextRunAt($EventName, $Callback)
2147 # if event is not a periodic event report failure to caller
2148 if (!array_key_exists($EventName, $this->EventPeriods)) {
return FALSE; }
2150 # retrieve last execution time for event if available
2151 $Signature = self::GetCallbackSignature($Callback);
2152 $LastRunTime = $this->DB->Query(
"SELECT LastRunAt FROM PeriodicEvents"
2153 .
" WHERE Signature = '".addslashes($Signature).
"'",
"LastRunAt");
2155 # if event was not found report failure to caller
2156 if ($LastRunTime === NULL) {
return FALSE; }
2158 # calculate next run time based on event period
2159 $NextRunTime = strtotime($LastRunTime) + $this->EventPeriods[$EventName];
2161 # report next run time to caller
2162 return $NextRunTime;
2180 function GetKnownPeriodicEvents()
2182 # retrieve last execution times
2183 $this->DB->Query(
"SELECT * FROM PeriodicEvents");
2184 $LastRunTimes = $this->DB->FetchColumn(
"LastRunAt",
"Signature");
2186 # for each known event
2188 foreach ($this->KnownPeriodicEvents as $Signature => $Info)
2190 # if last run time for event is available
2191 if (array_key_exists($Signature, $LastRunTimes))
2193 # calculate next run time for event
2194 $LastRun = strtotime($LastRunTimes[$Signature]);
2195 $NextRun = $LastRun + $this->EventPeriods[$Info[
"Period"]];
2196 if ($Info[
"Period"] ==
"EVENT_PERIODIC") { $LastRun = FALSE; }
2200 # set info to indicate run times are not known
2205 # add event info to list
2206 $Events[$Signature] = $Info;
2207 $Events[$Signature][
"LastRun"] = $LastRun;
2208 $Events[$Signature][
"NextRun"] = $NextRun;
2209 $Events[$Signature][
"Parameters"] = NULL;
2212 # return list of known events to caller
2219 # ---- Task Management ---------------------------------------------------
2224 const PRIORITY_HIGH = 1;
2226 const PRIORITY_MEDIUM = 2;
2228 const PRIORITY_LOW = 3;
2230 const PRIORITY_BACKGROUND = 4;
2244 function QueueTask($Callback, $Parameters = NULL,
2245 $Priority = self::PRIORITY_LOW, $Description =
"")
2247 # pack task info and write to database
2248 if ($Parameters === NULL) { $Parameters = array(); }
2249 $this->DB->Query(
"INSERT INTO TaskQueue"
2250 .
" (Callback, Parameters, Priority, Description)"
2251 .
" VALUES ('".addslashes(serialize($Callback)).
"', '"
2252 .addslashes(serialize($Parameters)).
"', ".intval($Priority).
", '"
2253 .addslashes($Description).
"')");
2273 function QueueUniqueTask($Callback, $Parameters = NULL,
2274 $Priority = self::PRIORITY_LOW, $Description =
"")
2276 if ($this->TaskIsInQueue($Callback, $Parameters))
2278 $QueryResult = $this->DB->Query(
"SELECT TaskId,Priority FROM TaskQueue"
2279 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'"
2280 .($Parameters ?
" AND Parameters = '"
2281 .addslashes(serialize($Parameters)).
"'" :
""));
2282 if ($QueryResult !== FALSE)
2284 $Record = $this->DB->FetchRow();
2285 if ($Record[
"Priority"] > $Priority)
2287 $this->DB->Query(
"UPDATE TaskQueue"
2288 .
" SET Priority = ".intval($Priority)
2289 .
" WHERE TaskId = ".intval($Record[
"TaskId"]));
2296 $this->QueueTask($Callback, $Parameters, $Priority, $Description);
2310 function TaskIsInQueue($Callback, $Parameters = NULL)
2312 $QueuedCount = $this->DB->Query(
2313 "SELECT COUNT(*) AS FoundCount FROM TaskQueue"
2314 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'"
2315 .($Parameters ?
" AND Parameters = '"
2316 .addslashes(serialize($Parameters)).
"'" :
""),
2318 $RunningCount = $this->DB->Query(
2319 "SELECT COUNT(*) AS FoundCount FROM RunningTasks"
2320 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'"
2321 .($Parameters ?
" AND Parameters = '"
2322 .addslashes(serialize($Parameters)).
"'" :
""),
2324 $FoundCount = $QueuedCount + $RunningCount;
2325 return ($FoundCount ? TRUE : FALSE);
2333 function GetTaskQueueSize($Priority = NULL)
2335 return $this->GetQueuedTaskCount(NULL, NULL, $Priority);
2345 function GetQueuedTaskList($Count = 100, $Offset = 0)
2347 return $this->GetTaskList(
"SELECT * FROM TaskQueue"
2348 .
" ORDER BY Priority, TaskId ", $Count, $Offset);
2364 function GetQueuedTaskCount($Callback = NULL,
2365 $Parameters = NULL, $Priority = NULL, $Description = NULL)
2367 $Query =
"SELECT COUNT(*) AS TaskCount FROM TaskQueue";
2369 if ($Callback !== NULL)
2371 $Query .= $Sep.
" Callback = '".addslashes(serialize($Callback)).
"'";
2374 if ($Parameters !== NULL)
2376 $Query .= $Sep.
" Parameters = '".addslashes(serialize($Parameters)).
"'";
2379 if ($Priority !== NULL)
2381 $Query .= $Sep.
" Priority = ".intval($Priority);
2384 if ($Description !== NULL)
2386 $Query .= $Sep.
" Description = '".addslashes($Description).
"'";
2388 return $this->DB->Query($Query,
"TaskCount");
2398 function GetRunningTaskList($Count = 100, $Offset = 0)
2400 return $this->GetTaskList(
"SELECT * FROM RunningTasks"
2401 .
" WHERE StartedAt >= '".date(
"Y-m-d H:i:s",
2402 (time() - ini_get(
"max_execution_time"))).
"'"
2403 .
" ORDER BY StartedAt", $Count, $Offset);
2413 function GetOrphanedTaskList($Count = 100, $Offset = 0)
2415 return $this->GetTaskList(
"SELECT * FROM RunningTasks"
2416 .
" WHERE StartedAt < '".date(
"Y-m-d H:i:s",
2417 (time() - ini_get(
"max_execution_time"))).
"'"
2418 .
" ORDER BY StartedAt", $Count, $Offset);
2425 function GetOrphanedTaskCount()
2427 return $this->DB->Query(
"SELECT COUNT(*) AS Count FROM RunningTasks"
2428 .
" WHERE StartedAt < '".date(
"Y-m-d H:i:s",
2429 (time() - ini_get(
"max_execution_time"))).
"'",
2438 function ReQueueOrphanedTask($TaskId, $NewPriority = NULL)
2440 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
2441 $this->DB->Query(
"INSERT INTO TaskQueue"
2442 .
" (Callback,Parameters,Priority,Description) "
2443 .
"SELECT Callback, Parameters, Priority, Description"
2444 .
" FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2445 if ($NewPriority !== NULL)
2447 $NewTaskId = $this->DB->LastInsertId();
2448 $this->DB->Query(
"UPDATE TaskQueue SET Priority = "
2449 .intval($NewPriority)
2450 .
" WHERE TaskId = ".intval($NewTaskId));
2452 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2453 $this->DB->Query(
"UNLOCK TABLES");
2460 function DeleteTask($TaskId)
2462 $this->DB->Query(
"DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2463 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2473 function GetTask($TaskId)
2475 # assume task will not be found
2478 # look for task in task queue
2479 $this->DB->Query(
"SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2481 # if task was not found in queue
2482 if (!$this->DB->NumRowsSelected())
2484 # look for task in running task list
2485 $this->DB->Query(
"SELECT * FROM RunningTasks WHERE TaskId = "
2490 if ($this->DB->NumRowsSelected())
2492 # if task was periodic
2493 $Row = $this->DB->FetchRow();
2494 if ($Row[
"Callback"] ==
2495 serialize(array(
"ApplicationFramework",
"PeriodicEventWrapper")))
2497 # unpack periodic task callback
2498 $WrappedCallback = unserialize($Row[
"Parameters"]);
2499 $Task[
"Callback"] = $WrappedCallback[1];
2500 $Task[
"Parameters"] = $WrappedCallback[2];
2504 # unpack task callback and parameters
2505 $Task[
"Callback"] = unserialize($Row[
"Callback"]);
2506 $Task[
"Parameters"] = unserialize($Row[
"Parameters"]);
2510 # return task to caller
2521 function TaskExecutionEnabled($NewValue =
DB_NOVALUE)
2523 return $this->UpdateSetting(
"TaskExecutionEnabled", $NewValue);
2533 return $this->UpdateSetting(
"MaxTasksRunning", $NewValue);
2543 static function GetTaskCallbackSynopsis($TaskInfo)
2545 # if task callback is function use function name
2546 $Callback = $TaskInfo[
"Callback"];
2548 if (!is_array($Callback))
2554 # if task callback is object
2555 if (is_object($Callback[0]))
2557 # if task callback is encapsulated ask encapsulation for name
2558 if (method_exists($Callback[0],
"GetCallbackAsText"))
2560 $Name = $Callback[0]->GetCallbackAsText();
2562 # else assemble name from object
2565 $Name = get_class($Callback[0]) .
"::" . $Callback[1];
2568 # else assemble name from supplied info
2571 $Name= $Callback[0] .
"::" . $Callback[1];
2575 # if parameter array was supplied
2576 $Parameters = $TaskInfo[
"Parameters"];
2577 $ParameterString =
"";
2578 if (is_array($Parameters))
2580 # assemble parameter string
2582 foreach ($Parameters as $Parameter)
2584 $ParameterString .= $Separator;
2585 if (is_int($Parameter) || is_float($Parameter))
2587 $ParameterString .= $Parameter;
2589 else if (is_string($Parameter))
2591 $ParameterString .=
"\"".htmlspecialchars($Parameter).
"\"";
2593 else if (is_array($Parameter))
2595 $ParameterString .=
"ARRAY";
2597 else if (is_object($Parameter))
2599 $ParameterString .=
"OBJECT";
2601 else if (is_null($Parameter))
2603 $ParameterString .=
"NULL";
2605 else if (is_bool($Parameter))
2607 $ParameterString .= $Parameter ?
"TRUE" :
"FALSE";
2609 else if (is_resource($Parameter))
2611 $ParameterString .= get_resource_type($Parameter);
2615 $ParameterString .=
"????";
2621 # assemble name and parameters and return result to caller
2622 return $Name.
"(".$ParameterString.
")";
2628 # ---- Clean URL Support -------------------------------------------------
2658 function AddCleanUrl($Pattern, $Page, $GetVars = NULL, $Template = NULL)
2660 # save clean URL mapping parameters
2661 $this->CleanUrlMappings[] = array(
2662 "Pattern" => $Pattern,
2664 "GetVars" => $GetVars,
2667 # if replacement template specified
2668 if ($Template !== NULL)
2670 # if GET parameters specified
2671 if (count($GetVars))
2673 # retrieve all possible permutations of GET parameters
2674 $GetPerms = $this->ArrayPermutations(array_keys($GetVars));
2676 # for each permutation of GET parameters
2677 foreach ($GetPerms as $VarPermutation)
2679 # construct search pattern for permutation
2680 $SearchPattern =
"/href=([\"'])index\\.php\\?P=".$Page;
2681 $GetVarSegment =
"";
2682 foreach ($VarPermutation as $GetVar)
2684 if (preg_match(
"%\\\$[0-9]+%", $GetVars[$GetVar]))
2686 $GetVarSegment .=
"&".$GetVar.
"=((?:(?!\\1)[^&])+)";
2690 $GetVarSegment .=
"&".$GetVar.
"=".$GetVars[$GetVar];
2693 $SearchPattern .= $GetVarSegment.
"\\1/i";
2695 # if template is actually a callback
2696 if (is_callable($Template))
2698 # add pattern to HTML output mod callbacks list
2699 $this->OutputModificationCallbacks[] = array(
2700 "Pattern" => $Pattern,
2702 "SearchPattern" => $SearchPattern,
2703 "Callback" => $Template,
2708 # construct replacement string for permutation
2709 $Replacement = $Template;
2711 foreach ($VarPermutation as $GetVar)
2713 $Replacement = str_replace(
2714 "\$".$GetVar,
"\$".$Index, $Replacement);
2717 $Replacement =
"href=\"".$Replacement.
"\"";
2719 # add pattern to HTML output modifications list
2720 $this->OutputModificationPatterns[] = $SearchPattern;
2721 $this->OutputModificationReplacements[] = $Replacement;
2727 # construct search pattern
2728 $SearchPattern =
"/href=\"index\\.php\\?P=".$Page.
"\"/i";
2730 # if template is actually a callback
2731 if (is_callable($Template))
2733 # add pattern to HTML output mod callbacks list
2734 $this->OutputModificationCallbacks[] = array(
2735 "Pattern" => $Pattern,
2737 "SearchPattern" => $SearchPattern,
2738 "Callback" => $Template,
2743 # add simple pattern to HTML output modifications list
2744 $this->OutputModificationPatterns[] = $SearchPattern;
2745 $this->OutputModificationReplacements[] =
"href=\"".$Template.
"\"";
2756 function CleanUrlIsMapped($Path)
2758 foreach ($this->CleanUrlMappings as $Info)
2760 if (preg_match($Info[
"Pattern"], $Path))
2777 function GetCleanUrlForPath($Path)
2779 # the search patterns and callbacks require a specific format
2780 $Format =
"href=\"".str_replace(
"&",
"&", $Path).
"\"";
2783 # perform any regular expression replacements on the search string
2784 $Search = preg_replace(
2785 $this->OutputModificationPatterns,
2786 $this->OutputModificationReplacements,
2789 # only run the callbacks if a replacement hasn't already been performed
2790 if ($Search == $Format)
2792 # perform any callback replacements on the search string
2793 foreach ($this->OutputModificationCallbacks as $Info)
2795 # make the information available to the callback
2796 $this->OutputModificationCallbackInfo = $Info;
2798 # execute the callback
2799 $Search = preg_replace_callback(
2800 $Info[
"SearchPattern"],
2801 array($this,
"OutputModificationCallbackShell"),
2806 # return the path untouched if no replacements were performed
2807 if ($Search == $Format)
2812 # remove the bits added to the search string to get it recognized by
2813 # the replacement expressions and callbacks
2814 $Result = substr($Search, 6, -1);
2825 function GetUncleanUrlForPath($Path)
2827 # for each clean URL mapping
2828 foreach ($this->CleanUrlMappings as $Info)
2830 # if current path matches the clean URL pattern
2831 if (preg_match($Info[
"Pattern"], $Path, $Matches))
2833 # the GET parameters for the URL, starting with the page name
2834 $GetVars = array(
"P" => $Info[
"Page"]);
2836 # if additional $_GET variables specified for clean URL
2837 if ($Info[
"GetVars"] !== NULL)
2839 # for each $_GET variable specified for clean URL
2840 foreach ($Info[
"GetVars"] as $VarName => $VarTemplate)
2842 # start with template for variable value
2843 $Value = $VarTemplate;
2845 # for each subpattern matched in current URL
2846 foreach ($Matches as $Index => $Match)
2848 # if not first (whole) match
2851 # make any substitutions in template
2852 $Value = str_replace(
"$".$Index, $Match, $Value);
2856 # add the GET variable
2857 $GetVars[$VarName] = $Value;
2861 # return the unclean URL
2862 return "index.php?" . http_build_query($GetVars);
2866 # return the path unchanged
2875 function GetCleanUrl()
2877 return $this->GetCleanUrlForPath($this->GetUncleanUrl());
2884 function GetUncleanUrl()
2886 $GetVars = array(
"P" => $this->GetPageName()) + $_GET;
2887 return "index.php?" . http_build_query($GetVars);
2893 # ---- Server Environment ------------------------------------------------
2902 static function SessionLifetime($NewValue = NULL)
2904 if ($NewValue !== NULL)
2906 self::$SessionLifetime = $NewValue;
2908 return self::$SessionLifetime;
2916 static function HtaccessSupport()
2918 # HTACCESS_SUPPORT is set in the .htaccess file
2919 return isset($_SERVER[
"HTACCESS_SUPPORT"]);
2929 static function RootUrl()
2931 # return override value if one is set
2932 if (self::$RootUrlOverride !== NULL)
2934 return self::$RootUrlOverride;
2937 # determine scheme name
2938 $Protocol = (isset($_SERVER[
"HTTPS"]) ?
"https" :
"http");
2940 # if HTTP_HOST is preferred or SERVER_NAME points to localhost
2941 # and HTTP_HOST is set
2942 if ((self::$PreferHttpHost || ($_SERVER[
"SERVER_NAME"] ==
"127.0.0.1"))
2943 && isset($_SERVER[
"HTTP_HOST"]))
2945 # use HTTP_HOST for domain name
2946 $DomainName = $_SERVER[
"HTTP_HOST"];
2950 # use SERVER_NAME for domain name
2951 $DomainName = $_SERVER[
"HTTP_HOST"];
2954 # build URL root and return to caller
2955 return $Protocol.
"://".$DomainName;
2972 static function RootUrlOverride($NewValue = self::NOVALUE)
2974 if ($NewValue !== self::NOVALUE)
2976 self::$RootUrlOverride = strlen(trim($NewValue)) ? $NewValue : NULL;
2978 return self::$RootUrlOverride;
2990 static function BaseUrl()
2992 $BaseUrl = self::RootUrl().dirname($_SERVER[
"SCRIPT_NAME"]);
2993 if (substr($BaseUrl, -1) !=
"/") { $BaseUrl .=
"/"; }
3004 static function FullUrl()
3006 return self::RootUrl().$_SERVER[
"REQUEST_URI"];
3019 static function PreferHttpHost($NewValue = NULL)
3021 if ($NewValue !== NULL)
3023 self::$PreferHttpHost = ($NewValue ? TRUE : FALSE);
3025 return self::$PreferHttpHost;
3032 static function BasePath()
3034 $BasePath = dirname($_SERVER[
"SCRIPT_NAME"]);
3036 if (substr($BasePath, -1) !=
"/")
3049 static function GetScriptUrl()
3051 if (array_key_exists(
"SCRIPT_URL", $_SERVER))
3053 return $_SERVER[
"SCRIPT_URL"];
3055 elseif (array_key_exists(
"REDIRECT_URL", $_SERVER))
3057 return $_SERVER[
"REDIRECT_URL"];
3059 elseif (array_key_exists(
"REQUEST_URI", $_SERVER))
3061 $Pieces = parse_url($_SERVER[
"REQUEST_URI"]);
3062 return $Pieces[
"path"];
3078 static function WasUrlRewritten($ScriptName=
"index.php")
3080 # needed to get the path of the URL minus the query and fragment pieces
3081 $Components = parse_url(self::GetScriptUrl());
3083 # if parsing was successful and a path is set
3084 if (is_array($Components) && isset($Components[
"path"]))
3086 $BasePath = self::BasePath();
3087 $Path = $Components[
"path"];
3089 # the URL was rewritten if the path isn't the base path, i.e., the
3090 # home page, and the file in the URL isn't the script generating the
3092 if ($BasePath != $Path && basename($Path) != $ScriptName)
3098 # the URL wasn't rewritten
3107 static function GetFreeMemory()
3109 return self::GetPhpMemoryLimit() - memory_get_usage();
3117 static function GetPhpMemoryLimit()
3119 $Str = strtoupper(ini_get(
"memory_limit"));
3120 if (substr($Str, -1) ==
"B") { $Str = substr($Str, 0, strlen($Str) - 1); }
3121 switch (substr($Str, -1))
3123 case "K": $MemoryLimit = (int)$Str * 1024;
break;
3124 case "M": $MemoryLimit = (int)$Str * 1048576;
break;
3125 case "G": $MemoryLimit = (int)$Str * 1073741824;
break;
3126 default: $MemoryLimit = (int)$Str;
break;
3128 return $MemoryLimit;
3138 function MaxExecutionTime($NewValue = NULL)
3140 if (func_num_args() && !ini_get(
"safe_mode"))
3142 if ($NewValue != $this->Settings[
"MaxExecTime"])
3144 $this->Settings[
"MaxExecTime"] = max($NewValue, 5);
3145 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
3146 .
" SET MaxExecTime = '"
3147 .intval($this->Settings[
"MaxExecTime"]).
"'");
3149 ini_set(
"max_execution_time", $this->Settings[
"MaxExecTime"]);
3150 set_time_limit($this->Settings[
"MaxExecTime"]);
3152 return ini_get(
"max_execution_time");
3158 # ---- Backward Compatibility --------------------------------------------
3166 function FindCommonTemplate($BaseName)
3168 return $this->FindFile(
3169 $this->IncludeDirList, $BaseName, array(
"tpl",
"html"));
3175 # ---- PRIVATE INTERFACE -------------------------------------------------
3177 private $AdditionalRequiredUIFiles = array();
3178 private $BackgroundTaskMemLeakLogThreshold = 10; # percentage of max mem
3179 private $BackgroundTaskMinFreeMemPercent = 25;
3180 private $BrowserDetectFunc;
3181 private $CacheCurrentPage = TRUE;
3182 private $CleanUrlMappings = array();
3183 private $CleanUrlRewritePerformed = FALSE;
3184 private $CssUrlFingerprintPath;
3186 private $DefaultPage =
"Home";
3187 private $DoNotMinimizeList = array();
3188 private $EnvIncludes = array();
3189 private $ExecutionStartTime;
3190 private $FoundUIFiles = array();
3191 private $HtmlCharset =
"UTF-8";
3192 private $JSMinimizerJavaScriptPackerAvailable = FALSE;
3193 private $JSMinimizerJShrinkAvailable = TRUE;
3194 private $JumpToPage = NULL;
3195 private $LogFileName =
"local/logs/site.log";
3196 private $MaxRunningTasksToTrack = 250;
3197 private $OutputModificationCallbackInfo;
3198 private $OutputModificationCallbacks = array();
3199 private $OutputModificationPatterns = array();
3200 private $OutputModificationReplacements = array();
3201 private $PageCacheTags = array();
3203 private $PostProcessingFuncs = array();
3204 private $RunningInBackground = FALSE;
3205 private $RunningTask;
3206 private $SavedContext;
3207 private $SaveTemplateLocationCache = FALSE;
3208 private $SessionStorage;
3209 private $SessionGcProbability;
3211 private $SuppressHTML = FALSE;
3212 private $TemplateLocationCache;
3213 private $TemplateLocationCacheInterval = 60; # in minutes
3214 private $TemplateLocationCacheExpiration;
3215 private $UnbufferedCallbacks = array();
3216 private $UrlFingerprintBlacklist = array();
3217 private $UseBaseTag = FALSE;
3219 private static $ActiveUI =
"default";
3220 private static $AppName =
"ScoutAF";
3221 private static $JSMinCacheDir =
"local/data/caches/JSMin";
3222 private static $ObjectDirectories = array();
3223 private static $ObjectLocationCache;
3224 private static $ObjectLocationCacheInterval = 60;
3225 private static $ObjectLocationCacheExpiration;
3226 private static $PreferHttpHost = FALSE;
3227 private static $RootUrlOverride = NULL;
3228 private static $SaveObjectLocationCache = FALSE;
3229 private static $ScssCacheDir =
"local/data/caches/SCSS";
3230 private static $SessionLifetime = 1440; # in seconds
3232 # offset used to generate page cache tag IDs from numeric tags
3233 const PAGECACHETAGIDOFFSET = 100000;
3239 private $NoTSR = FALSE;
3241 private $KnownPeriodicEvents = array();
3242 private $PeriodicEvents = array(
3243 "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
3244 "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
3245 "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
3246 "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
3247 "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
3249 private $EventPeriods = array(
3250 "EVENT_HOURLY" => 3600,
3251 "EVENT_DAILY" => 86400,
3252 "EVENT_WEEKLY" => 604800,
3253 "EVENT_MONTHLY" => 2592000,
3254 "EVENT_PERIODIC" => 0,
3256 private $UIEvents = array(
3257 "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
3258 "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
3259 "EVENT_PHP_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
3260 "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
3261 "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
3262 "EVENT_PAGE_OUTPUT_FILTER" => self::EVENTTYPE_CHAIN,
3269 private function LoadSettings()
3271 # read settings in from database
3272 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
3273 $this->Settings = $this->DB->FetchRow();
3275 # if settings were not previously initialized
3276 if ($this->Settings === FALSE)
3278 # initialize settings in database
3279 $this->DB->Query(
"INSERT INTO ApplicationFrameworkSettings"
3280 .
" (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
3282 # read new settings in from database
3283 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
3284 $this->Settings = $this->DB->FetchRow();
3286 # bail out if reloading new settings failed
3287 if ($this->Settings === FALSE)
3289 throw new Exception(
3290 "Unable to load application framework settings.");
3294 # if base path was not previously set or we appear to have moved
3295 if (!array_key_exists(
"BasePath", $this->Settings)
3296 || (!strlen($this->Settings[
"BasePath"]))
3297 || (!array_key_exists(
"BasePathCheck", $this->Settings))
3298 || (__FILE__ != $this->Settings[
"BasePathCheck"]))
3300 # attempt to extract base path from Apache .htaccess file
3301 if (is_readable(
".htaccess"))
3303 $Lines = file(
".htaccess");
3304 foreach ($Lines as $Line)
3306 if (preg_match(
"/\\s*RewriteBase\\s+/", $Line))
3308 $Pieces = preg_split(
3309 "/\\s+/", $Line, NULL, PREG_SPLIT_NO_EMPTY);
3310 $BasePath = $Pieces[1];
3315 # if base path was found
3316 if (isset($BasePath))
3318 # save base path locally
3319 $this->Settings[
"BasePath"] = $BasePath;
3321 # save base path to database
3322 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
3323 .
" SET BasePath = '".addslashes($BasePath).
"'"
3324 .
", BasePathCheck = '".addslashes(__FILE__).
"'");
3328 # retrieve template location cache
3329 $this->TemplateLocationCache = unserialize(
3330 $this->Settings[
"TemplateLocationCache"]);
3331 $this->TemplateLocationCacheInterval =
3332 $this->Settings[
"TemplateLocationCacheInterval"];
3333 $this->TemplateLocationCacheExpiration =
3334 strtotime($this->Settings[
"TemplateLocationCacheExpiration"]);
3336 # if template location cache looks invalid or has expired
3337 $CurrentTime = time();
3338 if (!count($this->TemplateLocationCache)
3339 || ($CurrentTime >= $this->TemplateLocationCacheExpiration))
3341 # clear cache and reset cache expiration
3342 $this->TemplateLocationCache = array();
3343 $this->TemplateLocationCacheExpiration =
3344 $CurrentTime + ($this->TemplateLocationCacheInterval * 60);
3345 $this->SaveTemplateLocationCache = TRUE;
3348 # retrieve object location cache
3349 self::$ObjectLocationCache =
3350 unserialize($this->Settings[
"ObjectLocationCache"]);
3351 self::$ObjectLocationCacheInterval =
3352 $this->Settings[
"ObjectLocationCacheInterval"];
3353 self::$ObjectLocationCacheExpiration =
3354 strtotime($this->Settings[
"ObjectLocationCacheExpiration"]);
3356 # if object location cache looks invalid or has expired
3357 if (!count(self::$ObjectLocationCache)
3358 || ($CurrentTime >= self::$ObjectLocationCacheExpiration))
3360 # clear cache and reset cache expiration
3361 self::$ObjectLocationCache = array();
3362 self::$ObjectLocationCacheExpiration =
3363 $CurrentTime + (self::$ObjectLocationCacheInterval * 60);
3364 self::$SaveObjectLocationCache = TRUE;
3374 private function RewriteCleanUrls($PageName)
3376 # if URL rewriting is supported by the server
3377 if ($this->HtaccessSupport())
3379 # retrieve current URL and remove base path if present
3380 $Url = $this->GetPageLocation();
3382 # for each clean URL mapping
3383 foreach ($this->CleanUrlMappings as $Info)
3385 # if current URL matches clean URL pattern
3386 if (preg_match($Info[
"Pattern"], $Url, $Matches))
3389 $PageName = $Info[
"Page"];
3391 # if $_GET variables specified for clean URL
3392 if ($Info[
"GetVars"] !== NULL)
3394 # for each $_GET variable specified for clean URL
3395 foreach ($Info[
"GetVars"] as $VarName => $VarTemplate)
3397 # start with template for variable value
3398 $Value = $VarTemplate;
3400 # for each subpattern matched in current URL
3401 foreach ($Matches as $Index => $Match)
3403 # if not first (whole) match
3406 # make any substitutions in template
3407 $Value = str_replace(
"$".$Index, $Match, $Value);
3411 # set $_GET variable
3412 $_GET[$VarName] = $Value;
3416 # set flag indicating clean URL mapped
3417 $this->CleanUrlRewritePerformed = TRUE;
3419 # stop looking for a mapping
3425 # return (possibly) updated page name to caller
3447 private function FindFile($DirectoryList, $BaseName,
3448 $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
3450 # generate template cache index for this page
3451 $CacheIndex = md5(serialize($DirectoryList))
3452 .
":".self::$ActiveUI.
":".$BaseName;
3454 # if caching is enabled and we have cached location
3455 if (($this->TemplateLocationCacheInterval > 0)
3456 && array_key_exists($CacheIndex,
3457 $this->TemplateLocationCache))
3459 # use template location from cache
3460 $FoundFileName = $this->TemplateLocationCache[$CacheIndex];
3464 # if suffixes specified and base name does not include suffix
3465 if (count($PossibleSuffixes)
3466 && !preg_match(
"/\.[a-zA-Z0-9]+$/", $BaseName))
3468 # add versions of file names with suffixes to file name list
3469 $FileNames = array();
3470 foreach ($PossibleSuffixes as $Suffix)
3472 $FileNames[] = $BaseName.
".".$Suffix;
3477 # use base name as file name
3478 $FileNames = array($BaseName);
3481 # if prefixes specified
3482 if (count($PossiblePrefixes))
3484 # add versions of file names with prefixes to file name list
3485 $NewFileNames = array();
3486 foreach ($FileNames as $FileName)
3488 foreach ($PossiblePrefixes as $Prefix)
3490 $NewFileNames[] = $Prefix.$FileName;
3493 $FileNames = $NewFileNames;
3496 # for each possible location
3497 $FoundFileName = NULL;
3498 foreach ($DirectoryList as $Dir)
3500 # substitute active UI name into path
3501 $Dir = str_replace(
"%ACTIVEUI%", self::$ActiveUI, $Dir);
3503 # for each possible file name
3504 foreach ($FileNames as $File)
3506 # if template is found at location
3507 if (file_exists($Dir.$File))
3509 # save full template file name and stop looking
3510 $FoundFileName = $Dir.$File;
3516 # save location in cache
3517 $this->TemplateLocationCache[$CacheIndex]
3520 # set flag indicating that cache should be saved
3521 $this->SaveTemplateLocationCache = TRUE;
3524 # return full template file name to caller
3525 return $FoundFileName;
3536 private function CompileScssFile($SrcFile)
3538 # build path to CSS file
3539 $DstFile = self::$ScssCacheDir.
"/".dirname($SrcFile)
3540 .
"/".basename($SrcFile);
3541 $DstFile = substr_replace($DstFile,
"css", -4);
3543 # if SCSS file is newer than CSS file
3544 if (!file_exists($DstFile)
3545 || (filemtime($SrcFile) > filemtime($DstFile)))
3547 # attempt to create CSS cache subdirectory if not present
3548 if (!is_dir(dirname($DstFile)))
3550 @mkdir(dirname($DstFile), 0777, TRUE);
3553 # if CSS cache directory and CSS file path appear writable
3554 static $CacheDirIsWritable;
3555 if (!isset($CacheDirIsWritable))
3556 { $CacheDirIsWritable = is_writable(self::$ScssCacheDir); }
3557 if (is_writable($DstFile)
3558 || (!file_exists($DstFile) && $CacheDirIsWritable))
3560 # load SCSS and compile to CSS
3561 $ScssCode = file_get_contents($SrcFile);
3562 $ScssCompiler =
new scssc();
3563 $ScssCompiler->setFormatter($this->GenerateCompactCss()
3564 ?
"scss_formatter_compressed" :
"scss_formatter");
3567 $CssCode = $ScssCompiler->compile($ScssCode);
3569 # add fingerprinting for URLs in CSS
3570 $this->CssUrlFingerprintPath = dirname($SrcFile);
3571 $CssCode = preg_replace_callback(
3572 "/url\((['\"]*)(.+)\.([a-z]+)(['\"]*)\)/",
3573 array($this,
"CssUrlFingerprintInsertion"),
3576 # write out CSS file
3577 file_put_contents($DstFile, $CssCode);
3579 catch (Exception $Ex)
3581 $this->LogError(self::LOGLVL_ERROR,
3582 "Error compiling SCSS file ".$SrcFile.
": "
3583 .$Ex->getMessage());
3589 # log error and set CSS file path to indicate failure
3590 $this->LogError(self::LOGLVL_ERROR,
3591 "Unable to write out CSS file for SCSS file ".$SrcFile);
3596 # return CSS file path to caller
3607 private function MinimizeJavascriptFile($SrcFile)
3609 # bail out if file is on exclusion list
3610 foreach ($this->DoNotMinimizeList as $DNMFile)
3612 if (($SrcFile == $DNMFile) || (basename($SrcFile) == $DNMFile))
3618 # build path to minimized file
3619 $DstFile = self::$JSMinCacheDir.
"/".dirname($SrcFile)
3620 .
"/".basename($SrcFile);
3621 $DstFile = substr_replace($DstFile,
".min", -3, 0);
3623 # if original file is newer than minimized file
3624 if (!file_exists($DstFile)
3625 || (filemtime($SrcFile) > filemtime($DstFile)))
3627 # attempt to create cache subdirectory if not present
3628 if (!is_dir(dirname($DstFile)))
3630 @mkdir(dirname($DstFile), 0777, TRUE);
3633 # if cache directory and minimized file path appear writable
3634 static $CacheDirIsWritable;
3635 if (!isset($CacheDirIsWritable))
3636 { $CacheDirIsWritable = is_writable(self::$JSMinCacheDir); }
3637 if (is_writable($DstFile)
3638 || (!file_exists($DstFile) && $CacheDirIsWritable))
3640 # load JavaScript code
3641 $Code = file_get_contents($SrcFile);
3643 # decide which minimizer to use
3644 if ($this->JSMinimizerJavaScriptPackerAvailable
3645 && $this->JSMinimizerJShrinkAvailable)
3647 $Minimizer = (strlen($Code) < 5000)
3648 ?
"JShrink" :
"JavaScriptPacker";
3650 elseif ($this->JSMinimizerJShrinkAvailable)
3652 $Minimizer =
"JShrink";
3656 $Minimizer =
"NONE";
3662 case "JavaScriptMinimizer":
3664 $MinimizedCode = $Packer->pack();
3672 catch (Exception $Exception)
3674 unset($MinimizedCode);
3675 $MinimizeError = $Exception->getMessage();
3680 # if minimization succeeded
3681 if (isset($MinimizedCode))
3683 # write out minimized file
3684 file_put_contents($DstFile, $MinimizedCode);
3688 # log error and set destination file path to indicate failure
3689 $ErrMsg =
"Unable to minimize JavaScript file ".$SrcFile;
3690 if (isset($MinimizeError))
3692 $ErrMsg .=
" (".$MinimizeError.
")";
3694 $this->LogError(self::LOGLVL_ERROR, $ErrMsg);
3700 # log error and set destination file path to indicate failure
3701 $this->LogError(self::LOGLVL_ERROR,
3702 "Unable to write out minimized JavaScript for file ".$SrcFile);
3707 # return CSS file path to caller
3718 private function CssUrlFingerprintInsertion($Matches)
3720 # generate fingerprint string from CSS file modification time
3721 $FileName = realpath($this->CssUrlFingerprintPath.
"/".
3722 $Matches[2].
".".$Matches[3]);
3723 $MTime = filemtime($FileName);
3724 $Fingerprint = sprintf(
"%06X", ($MTime % 0xFFFFFF));
3726 # build URL string with fingerprint and return it to caller
3727 return "url(".$Matches[1].$Matches[2].
".".$Fingerprint
3728 .
".".$Matches[3].$Matches[4].
")";
3737 private function GetRequiredFilesNotYetLoaded($PageContentFile)
3739 # start out assuming no files required
3740 $RequiredFiles = array();
3742 # if page content file supplied
3743 if ($PageContentFile)
3745 # if file containing list of required files is available
3746 $Path = dirname($PageContentFile);
3747 $RequireListFile = $Path.
"/REQUIRES";
3748 if (file_exists($RequireListFile))
3750 # read in list of required files
3751 $RequestedFiles = file($RequireListFile);
3753 # for each line in required file list
3754 foreach ($RequestedFiles as $Line)
3756 # if line is not a comment
3757 $Line = trim($Line);
3758 if (!preg_match(
"/^#/", $Line))
3760 # if file has not already been loaded
3761 if (!in_array($Line, $this->FoundUIFiles))
3763 # add to list of required files
3764 $RequiredFiles[] = $Line;
3771 # add in additional required files if any
3772 if (count($this->AdditionalRequiredUIFiles))
3774 # make sure there are no duplicates
3775 $AdditionalRequiredUIFiles = array_unique(
3776 $this->AdditionalRequiredUIFiles);
3778 $RequiredFiles = array_merge(
3779 $RequiredFiles, $AdditionalRequiredUIFiles);
3782 # return list of required files to caller
3783 return $RequiredFiles;
3789 private function SetUpObjectAutoloading()
3792 function __autoload($ClassName)
3795 ApplicationFramework::AutoloadObjects($ClassName);
3805 static function AutoloadObjects($ClassName)
3807 # if caching is not turned off
3808 # and we have a cached location for class
3809 # and file at cached location is readable
3810 if ((self::$ObjectLocationCacheInterval > 0)
3811 && array_key_exists($ClassName,
3812 self::$ObjectLocationCache)
3813 && is_readable(self::$ObjectLocationCache[$ClassName]))
3815 # use object location from cache
3816 require_once(self::$ObjectLocationCache[$ClassName]);
3820 # convert any namespace separators in class name
3821 $ClassName = str_replace(
"\\",
"-", $ClassName);
3823 # for each possible object file directory
3825 foreach (self::$ObjectDirectories as $Location => $Info)
3827 # make any needed replacements in directory path
3828 $Location = str_replace(
"%ACTIVEUI%", self::$ActiveUI, $Location);
3830 # if directory looks valid
3831 if (is_dir($Location))
3833 # build class file name
3834 $NewClassName = ($Info[
"ClassPattern"] && $Info[
"ClassReplacement"])
3835 ? preg_replace($Info[
"ClassPattern"],
3836 $Info[
"ClassReplacement"], $ClassName)
3839 # read in directory contents if not already retrieved
3840 if (!isset($FileLists[$Location]))
3842 $FileLists[$Location] = self::ReadDirectoryTree(
3843 $Location,
'/^.+\.php$/i');
3846 # for each file in target directory
3847 $FileNames = $FileLists[$Location];
3848 $TargetName = strtolower($Info[
"Prefix"].$NewClassName.
".php");
3849 foreach ($FileNames as $FileName)
3851 # if file matches our target object file name
3852 if (strtolower($FileName) == $TargetName)
3854 # include object file
3855 require_once($Location.$FileName);
3857 # save location to cache
3858 self::$ObjectLocationCache[$ClassName]
3859 = $Location.$FileName;
3861 # set flag indicating that cache should be saved
3862 self::$SaveObjectLocationCache = TRUE;
3881 private static function ReadDirectoryTree($Directory, $Pattern)
3883 $CurrentDir = getcwd();
3885 $DirIter =
new RecursiveDirectoryIterator(
".");
3886 $IterIter =
new RecursiveIteratorIterator($DirIter);
3887 $RegexResults =
new RegexIterator($IterIter, $Pattern,
3888 RecursiveRegexIterator::GET_MATCH);
3889 $FileList = array();
3890 foreach ($RegexResults as $Result)
3892 $FileList[] = substr($Result[0], 2);
3901 private function UndoMagicQuotes()
3903 # if this PHP version has magic quotes support
3904 if (version_compare(PHP_VERSION,
"5.4.0",
"<"))
3906 # turn off runtime magic quotes if on
3907 if (get_magic_quotes_runtime())
3910 set_magic_quotes_runtime(FALSE);
3914 # if magic quotes GPC is on
3915 if (get_magic_quotes_gpc())
3917 # strip added slashes from incoming variables
3918 $GPC = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
3919 array_walk_recursive($GPC,
3920 array($this,
"UndoMagicQuotes_StripCallback"));
3924 private function UndoMagicQuotes_StripCallback(&$Value)
3926 $Value = stripslashes($Value);
3933 private function LoadUIFunctions()
3936 "local/interface/%ACTIVEUI%/include",
3937 "interface/%ACTIVEUI%/include",
3938 "local/interface/default/include",
3939 "interface/default/include",
3941 foreach ($Dirs as $Dir)
3943 $Dir = str_replace(
"%ACTIVEUI%", self::$ActiveUI, $Dir);
3946 $FileNames = scandir($Dir);
3947 foreach ($FileNames as $FileName)
3949 if (preg_match(
"/^F-([A-Za-z0-9_]+)\.php/",
3950 $FileName, $Matches)
3951 || preg_match(
"/^F-([A-Za-z0-9_]+)\.html/",
3952 $FileName, $Matches))
3954 if (!function_exists($Matches[1]))
3956 include_once($Dir.
"/".$FileName);
3969 private function ProcessPeriodicEvent($EventName, $Callback)
3971 # retrieve last execution time for event if available
3972 $Signature = self::GetCallbackSignature($Callback);
3973 $LastRun = $this->DB->Query(
"SELECT LastRunAt FROM PeriodicEvents"
3974 .
" WHERE Signature = '".addslashes($Signature).
"'",
"LastRunAt");
3976 # determine whether enough time has passed for event to execute
3977 $ShouldExecute = (($LastRun === NULL)
3978 || (time() > (strtotime($LastRun) + $this->EventPeriods[$EventName])))
3981 # if event should run
3984 # add event to task queue
3985 $WrapperCallback = array(
"ApplicationFramework",
"PeriodicEventWrapper");
3986 $WrapperParameters = array(
3987 $EventName, $Callback, array(
"LastRunAt" => $LastRun));
3988 $this->QueueUniqueTask($WrapperCallback, $WrapperParameters);
3991 # add event to list of periodic events
3992 $this->KnownPeriodicEvents[$Signature] = array(
3993 "Period" => $EventName,
3994 "Callback" => $Callback,
3995 "Queued" => $ShouldExecute);
4005 private static function PeriodicEventWrapper($EventName, $Callback, $Parameters)
4008 if (!isset($DB)) { $DB =
new Database(); }
4011 $ReturnVal = call_user_func_array($Callback, $Parameters);
4013 # if event is already in database
4014 $Signature = self::GetCallbackSignature($Callback);
4015 if ($DB->Query(
"SELECT COUNT(*) AS EventCount FROM PeriodicEvents"
4016 .
" WHERE Signature = '".addslashes($Signature).
"'",
"EventCount"))
4018 # update last run time for event
4019 $DB->Query(
"UPDATE PeriodicEvents SET LastRunAt = "
4020 .(($EventName ==
"EVENT_PERIODIC")
4021 ?
"'".date(
"Y-m-d H:i:s", time() + ($ReturnVal * 60)).
"'"
4023 .
" WHERE Signature = '".addslashes($Signature).
"'");
4027 # add last run time for event to database
4028 $DB->Query(
"INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES "
4029 .
"('".addslashes($Signature).
"', "
4030 .(($EventName ==
"EVENT_PERIODIC")
4031 ?
"'".date(
"Y-m-d H:i:s", time() + ($ReturnVal * 60)).
"'"
4041 private static function GetCallbackSignature($Callback)
4043 return !is_array($Callback) ? $Callback
4044 : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
4052 private function PrepForTSR()
4054 # if HTML has been output and it's time to launch another task
4055 # (only TSR if HTML has been output because otherwise browsers
4056 # may misbehave after connection is closed)
4057 if (($this->JumpToPage || !$this->SuppressHTML)
4058 && (time() > (strtotime($this->Settings[
"LastTaskRunAt"])
4059 + (ini_get(
"max_execution_time")
4060 / $this->Settings[
"MaxTasksRunning"]) + 5))
4061 && $this->GetTaskQueueSize()
4062 && $this->Settings[
"TaskExecutionEnabled"])
4064 # begin buffering output for TSR
4067 # let caller know it is time to launch another task
4072 # let caller know it is not time to launch another task
4081 private function LaunchTSR()
4083 # set headers to close out connection to browser
4086 ignore_user_abort(TRUE);
4087 header(
"Connection: close");
4088 header(
"Content-Length: ".ob_get_length());
4091 # output buffered content
4092 while (ob_get_level()) { ob_end_flush(); }
4095 # write out any outstanding data and end HTTP session
4096 session_write_close();
4098 # set flag indicating that we are now running in background
4099 $this->RunningInBackground = TRUE;
4101 # handle garbage collection for session data
4102 if (isset($this->SessionStorage) &&
4103 (rand()/getrandmax()) <= $this->SessionGcProbability)
4105 # determine when sessions will expire
4106 $ExpiredTime = strtotime(
"-". self::$SessionLifetime.
" seconds");
4108 # iterate over the files in our session storage, deleting the expired
4109 # ones (sess_ is php's session prefix)
4110 foreach (scandir($this->SessionStorage) as $TgtFile)
4112 $FullPath = $this->SessionStorage.
"/".$TgtFile;
4113 if ( is_file($FullPath) &&
4114 preg_match(
"/^sess_/", $TgtFile) &&
4115 filectime($FullPath) < $ExpiredTime)
4122 # if there is still a task in the queue
4123 if ($this->GetTaskQueueSize())
4125 # garbage collect to give as much memory as possible for tasks
4126 if (function_exists(
"gc_collect_cycles")) { gc_collect_cycles(); }
4128 # turn on output buffering to (hopefully) record any crash output
4131 # lock tables and grab last task run time to double check
4132 $this->DB->Query(
"LOCK TABLES ApplicationFrameworkSettings WRITE");
4133 $this->LoadSettings();
4135 # if still time to launch another task
4136 if (time() > (strtotime($this->Settings[
"LastTaskRunAt"])
4137 + (ini_get(
"max_execution_time")
4138 / $this->Settings[
"MaxTasksRunning"]) + 5))
4140 # update the "last run" time and release tables
4141 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
4142 .
" SET LastTaskRunAt = '".date(
"Y-m-d H:i:s").
"'");
4143 $this->DB->Query(
"UNLOCK TABLES");
4145 # run tasks while there is a task in the queue
4146 # and enough time and memory left
4150 $this->RunNextTask();
4152 # calculate percentage of memory still available
4153 $PercentFreeMem = (self::GetFreeMemory()
4154 / self::GetPhpMemoryLimit()) * 100;
4156 while ($this->GetTaskQueueSize()
4157 && ($this->GetSecondsBeforeTimeout() > 65)
4158 && ($PercentFreeMem > $this->BackgroundTaskMinFreeMemPercent));
4163 $this->DB->Query(
"UNLOCK TABLES");
4175 private function GetTaskList($DBQuery, $Count, $Offset)
4177 $this->DB->Query($DBQuery.
" LIMIT ".intval($Offset).
",".intval($Count));
4179 while ($Row = $this->DB->FetchRow())
4181 $Tasks[$Row[
"TaskId"]] = $Row;
4182 if ($Row[
"Callback"] ==
4183 serialize(array(
"ApplicationFramework",
"PeriodicEventWrapper")))
4185 $WrappedCallback = unserialize($Row[
"Parameters"]);
4186 $Tasks[$Row[
"TaskId"]][
"Callback"] = $WrappedCallback[1];
4187 $Tasks[$Row[
"TaskId"]][
"Parameters"] = NULL;
4191 $Tasks[$Row[
"TaskId"]][
"Callback"] = unserialize($Row[
"Callback"]);
4192 $Tasks[$Row[
"TaskId"]][
"Parameters"] = unserialize($Row[
"Parameters"]);
4201 private function RunNextTask()
4203 # lock tables to prevent same task from being run by multiple sessions
4204 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
4206 # look for task at head of queue
4207 $this->DB->Query(
"SELECT * FROM TaskQueue ORDER BY Priority, TaskId LIMIT 1");
4208 $Task = $this->DB->FetchRow();
4210 # if there was a task available
4213 # move task from queue to running tasks list
4214 $this->DB->Query(
"INSERT INTO RunningTasks "
4215 .
"(TaskId,Callback,Parameters,Priority,Description) "
4216 .
"SELECT * FROM TaskQueue WHERE TaskId = "
4217 .intval($Task[
"TaskId"]));
4218 $this->DB->Query(
"DELETE FROM TaskQueue WHERE TaskId = "
4219 .intval($Task[
"TaskId"]));
4221 # release table locks to again allow other sessions to run tasks
4222 $this->DB->Query(
"UNLOCK TABLES");
4224 # unpack stored task info
4225 $Callback = unserialize($Task[
"Callback"]);
4226 $Parameters = unserialize($Task[
"Parameters"]);
4228 # attempt to load task callback if not already available
4229 $this->LoadFunction($Callback);
4231 # save amount of free memory for later comparison
4232 $BeforeFreeMem = self::GetFreeMemory();
4235 $this->RunningTask = $Task;
4238 call_user_func_array($Callback, $Parameters);
4242 call_user_func($Callback);
4244 unset($this->RunningTask);
4246 # log if task leaked significant memory
4247 if (function_exists(
"gc_collect_cycles")) { gc_collect_cycles(); }
4248 $AfterFreeMem = self::GetFreeMemory();
4249 $LeakThreshold = self::GetPhpMemoryLimit()
4250 * ($this->BackgroundTaskMemLeakLogThreshold / 100);
4251 if (($BeforeFreeMem - $AfterFreeMem) > $LeakThreshold)
4253 $this->LogError(self::LOGLVL_DEBUG,
"Task "
4254 .self::GetTaskCallbackSynopsis(
4255 $this->GetTask($Task[
"TaskId"])).
" leaked "
4256 .number_format($BeforeFreeMem - $AfterFreeMem).
" bytes.");
4259 # remove task from running tasks list
4260 $this->DB->Query(
"DELETE FROM RunningTasks"
4261 .
" WHERE TaskId = ".intval($Task[
"TaskId"]));
4263 # prune running tasks list if necessary
4264 $RunningTasksCount = $this->DB->Query(
4265 "SELECT COUNT(*) AS TaskCount FROM RunningTasks",
"TaskCount");
4266 if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
4268 $this->DB->Query(
"DELETE FROM RunningTasks ORDER BY StartedAt"
4269 .
" LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
4274 # release table locks to again allow other sessions to run tasks
4275 $this->DB->Query(
"UNLOCK TABLES");
4286 # attempt to remove any memory limits
4287 $FreeMemory = $this->GetFreeMemory();
4288 ini_set(
"memory_limit", -1);
4290 # if there is a background task currently running
4291 if (isset($this->RunningTask))
4293 # add info about current page load
4294 $CrashInfo[
"ElapsedTime"] = $this->GetElapsedExecutionTime();
4295 $CrashInfo[
"FreeMemory"] = $FreeMemory;
4296 $CrashInfo[
"REMOTE_ADDR"] = $_SERVER[
"REMOTE_ADDR"];
4297 $CrashInfo[
"REQUEST_URI"] = $_SERVER[
"REQUEST_URI"];
4298 if (isset($_SERVER[
"REQUEST_TIME"]))
4300 $CrashInfo[
"REQUEST_TIME"] = $_SERVER[
"REQUEST_TIME"];
4302 if (isset($_SERVER[
"REMOTE_HOST"]))
4304 $CrashInfo[
"REMOTE_HOST"] = $_SERVER[
"REMOTE_HOST"];
4307 # add info about error that caused crash (if available)
4308 if (function_exists(
"error_get_last"))
4310 $CrashInfo[
"LastError"] = error_get_last();
4313 # add info about current output buffer contents (if available)
4314 if (ob_get_length() !== FALSE)
4316 $CrashInfo[
"OutputBuffer"] = ob_get_contents();
4319 # if backtrace info is available for the crash
4320 $Backtrace = debug_backtrace();
4321 if (count($Backtrace) > 1)
4323 # discard the current context from the backtrace
4324 array_shift($Backtrace);
4326 # add the backtrace to the crash info
4327 $CrashInfo[
"Backtrace"] = $Backtrace;
4329 # else if saved backtrace info is available
4330 elseif (isset($this->SavedContext))
4332 # add the saved backtrace to the crash info
4333 $CrashInfo[
"Backtrace"] = $this->SavedContext;
4336 # save crash info for currently running task
4338 $DB->Query(
"UPDATE RunningTasks SET CrashInfo = '"
4339 .addslashes(serialize($CrashInfo))
4340 .
"' WHERE TaskId = ".intval($this->RunningTask[
"TaskId"]));
4363 private function AddToDirList($DirList, $Dir, $SearchLast, $SkipSlashCheck)
4365 # convert incoming directory to array of directories (if needed)
4366 $Dirs = is_array($Dir) ? $Dir : array($Dir);
4368 # reverse array so directories are searched in specified order
4369 $Dirs = array_reverse($Dirs);
4371 # for each directory
4372 foreach ($Dirs as $Location)
4374 # make sure directory includes trailing slash
4375 if (!$SkipSlashCheck)
4377 $Location = $Location
4378 .((substr($Location, -1) !=
"/") ?
"/" :
"");
4381 # remove directory from list if already present
4382 if (in_array($Location, $DirList))
4384 $DirList = array_diff(
4385 $DirList, array($Location));
4388 # add directory to list of directories
4391 array_push($DirList, $Location);
4395 array_unshift($DirList, $Location);
4399 # return updated directory list to caller
4410 private function ArrayPermutations(
$Items, $Perms = array())
4414 $Result = array($Perms);
4419 for ($Index = count(
$Items) - 1; $Index >= 0; --$Index)
4423 list($Segment) = array_splice($NewItems, $Index, 1);
4424 array_unshift($NewPerms, $Segment);
4425 $Result = array_merge($Result,
4426 $this->ArrayPermutations($NewItems, $NewPerms));
4438 private function OutputModificationCallbackShell($Matches)
4440 # call previously-stored external function
4441 return call_user_func($this->OutputModificationCallbackInfo[
"Callback"],
4443 $this->OutputModificationCallbackInfo[
"Pattern"],
4444 $this->OutputModificationCallbackInfo[
"Page"],
4445 $this->OutputModificationCallbackInfo[
"SearchPattern"]);
4454 function UpdateSetting($FieldName, $NewValue =
DB_NOVALUE)
4456 return $this->DB->UpdateValue(
"ApplicationFrameworkSettings",
4457 $FieldName, $NewValue, NULL, $this->Settings);
4461 private $InterfaceDirList = array(
4462 "local/interface/%ACTIVEUI%/",
4463 "interface/%ACTIVEUI%/",
4464 "local/interface/default/",
4465 "interface/default/",
4471 private $IncludeDirList = array(
4472 "local/interface/%ACTIVEUI%/include/",
4473 "interface/%ACTIVEUI%/include/",
4474 "interface/%ACTIVEUI%/objects/",
4475 "local/interface/default/include/",
4476 "interface/default/include/",
4477 "interface/default/objects/",
4480 private $ImageDirList = array(
4481 "local/interface/%ACTIVEUI%/images/",
4482 "interface/%ACTIVEUI%/images/",
4483 "local/interface/default/images/",
4484 "interface/default/images/",
4487 private $FunctionDirList = array(
4488 "local/interface/%ACTIVEUI%/include/",
4489 "interface/%ACTIVEUI%/include/",
4490 "local/interface/default/include/",
4491 "interface/default/include/",
4496 const NOVALUE =
".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
4499 # ---- Page Caching (Internal Methods) -----------------------------------
4506 private function CheckForCachedPage($PageName)
4508 # assume no cached page will be found
4511 # if returning a cached page is allowed
4512 if ($this->CacheCurrentPage)
4514 # get fingerprint for requested page
4515 $PageFingerprint = $this->GetPageFingerprint($PageName);
4517 # look for matching page in cache in database
4518 $this->DB->Query(
"SELECT * FROM AF_CachedPages"
4519 .
" WHERE Fingerprint = '".addslashes($PageFingerprint).
"'");
4521 # if matching page found
4522 if ($this->DB->NumRowsSelected())
4524 # if cached page has expired
4525 $Row = $this->DB->FetchRow();
4526 $ExpirationTime = strtotime(
4527 "-".$this->PageCacheExpirationPeriod().
" seconds");
4528 if (strtotime($Row[
"CachedAt"]) < $ExpirationTime)
4530 # clear expired pages from cache
4531 $ExpirationTimestamp = date(
"Y-m-d H:i:s", $ExpirationTime);
4532 $this->DB->Query(
"DELETE CP, CPTI FROM AF_CachedPages CP,"
4533 .
" AF_CachedPageTagInts CPTI"
4534 .
" WHERE CP.CachedAt < '".$ExpirationTimestamp.
"'"
4535 .
" AND CPTI.CacheId = CP.CacheId");
4536 $this->DB->Query(
"DELETE FROM AF_CachedPages "
4537 .
" WHERE CachedAt < '".$ExpirationTimestamp.
"'");
4541 # display cached page and exit
4542 $CachedPage = $Row[
"PageContent"];
4547 # return any cached page found to caller
4556 private function UpdatePageCache($PageName, $PageContent)
4558 # if page caching is enabled and current page should be cached
4559 if ($this->PageCacheEnabled()
4560 && $this->CacheCurrentPage
4561 && ($PageName !=
"404"))
4563 # save page to cache
4564 $PageFingerprint = $this->GetPageFingerprint($PageName);
4565 $this->DB->Query(
"INSERT INTO AF_CachedPages"
4566 .
" (Fingerprint, PageContent) VALUES"
4567 .
" ('".$this->DB->EscapeString($PageFingerprint).
"', '"
4568 .$this->DB->EscapeString($PageContent).
"')");
4569 $CacheId = $this->DB->LastInsertId();
4571 # for each page cache tag that was added
4572 foreach ($this->PageCacheTags as $Tag => $Pages)
4574 # if current page is in list for tag
4575 if (in_array(
"CURRENT", $Pages) || in_array($PageName, $Pages))
4578 $TagId = $this->GetPageCacheTagId($Tag);
4580 # mark current page as associated with tag
4581 $this->DB->Query(
"INSERT INTO AF_CachedPageTagInts"
4582 .
" (TagId, CacheId) VALUES "
4583 .
" (".intval($TagId).
", ".intval($CacheId).
")");
4594 private function GetPageCacheTagId($Tag)
4596 # if tag is a non-negative integer
4597 if (is_numeric($Tag) && ($Tag > 0) && (intval($Tag) == $Tag))
4600 $Id = self::PAGECACHETAGIDOFFSET + $Tag;
4604 # look up ID in database
4605 $Id = $this->DB->Query(
"SELECT TagId FROM AF_CachedPageTags"
4606 .
" WHERE Tag = '".addslashes($Tag).
"'",
"TagId");
4608 # if ID was not found
4611 # add tag to database
4612 $this->DB->Query(
"INSERT INTO AF_CachedPageTags"
4613 .
" SET Tag = '".addslashes($Tag).
"'");
4614 $Id = $this->DB->LastInsertId();
4618 # return tag ID to caller
4627 private function GetPageFingerprint($PageName)
4629 # only get the environmental fingerprint once so that it is consistent
4630 # between page construction start and end
4631 static $EnvFingerprint;
4632 if (!isset($EnvFingerprint))
4634 $EnvFingerprint = md5(json_encode($_GET).json_encode($_POST));
4637 # build page fingerprint and return it to caller
4638 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.