CWIS Developer Documentation

- Home
- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

ApplicationFramework.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: ApplicationFramework.php
4 #
5 # Part of the ScoutLib application support library
6 # Copyright 2009-2016 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu
8 #
9
14 class ApplicationFramework
15 {
16
17 # ---- PUBLIC INTERFACE --------------------------------------------------
18 /*@(*/
20
25 public function __construct()
26 {
27 # set up a class alias for convenience
28 class_alias("ApplicationFramework", "AF");
29
30 # make sure default time zone is set
31 # (using CST if nothing set because we have to use something
32 # and Scout is based in Madison, WI, which is in CST)
33 if ((ini_get("date.timezone") === NULL)
34 || !strlen(ini_get("date.timezone")))
35 {
36 ini_set("date.timezone", "America/Chicago");
37 }
38
39 # save execution start time
40 $this->ExecutionStartTime = microtime(TRUE);
41
42 # set up default object file search locations
43 self::AddObjectDirectory("local/interface/%ACTIVEUI%/objects");
44 self::AddObjectDirectory("interface/%ACTIVEUI%/objects");
45 self::AddObjectDirectory("local/interface/%DEFAULTUI%/objects");
46 self::AddObjectDirectory("interface/%DEFAULTUI%/objects");
47 self::AddObjectDirectory("local/objects");
48 self::AddObjectDirectory("objects");
49
50 # set up object file autoloader
51 spl_autoload_register(array($this, "AutoloadObjects"));
52
53 # set up function to output any buffered text in case of crash
54 register_shutdown_function(array($this, "OnCrash"));
55
56 # if we were not invoked via command line interface
57 if (php_sapi_name() !== "cli")
58 {
59 # build cookie domain string
60 $SessionDomain = isset($_SERVER["SERVER_NAME"]) ? $_SERVER["SERVER_NAME"]
61 : isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"]
62 : php_uname("n");
63
64 # include a leading period so that older browsers implementing
65 # rfc2109 do not reject our cookie
66 $SessionDomain = ".".$SessionDomain;
67
68 # if it appears our session storage area is writable
69 if (is_writable(session_save_path()))
70 {
71 # store our session files in a subdirectory to avoid
72 # accidentally sharing sessions with other installations
73 # on the same domain
74 $SessionStorage = session_save_path()
75 ."/".self::$AppName."_".md5($SessionDomain.dirname(__FILE__));
76
77 # create session storage subdirectory if not found
78 if (!is_dir($SessionStorage)) { mkdir($SessionStorage, 0700 ); }
79
80 # if session storage subdirectory is writable
81 if (is_writable($SessionStorage))
82 {
83 # save parameters of our session storage as instance variables
84 # for later use
85 $this->SessionGcProbability =
86 ini_get("session.gc_probability") / ini_get("session.gc_divisor");
87 # require a gc probability of at least MIN_GC_PROBABILITY
88 if ($this->SessionGcProbability < self::MIN_GC_PROBABILITY)
89 {
90 $this->SessionGcProbability = self::MIN_GC_PROBABILITY;
91 }
92
93 $this->SessionStorage = $SessionStorage;
94
95 # set the new session storage location
96 session_save_path($SessionStorage);
97
98 # disable PHP's garbage collection, as it does not handle
99 # subdirectories (instead, we'll do the cleanup as we run
100 # background tasks)
101 ini_set("session.gc_probability", 0);
102 }
103 }
104
105 # set garbage collection max period to our session lifetime
106 ini_set("session.gc_maxlifetime", self::$SessionLifetime);
107
108 # limit cookie to secure connection if we are running over same
109 $SecureCookie = isset($_SERVER["HTTPS"]) ? TRUE : FALSE;
110
111 # Cookies lacking embedded dots are... fun.
112 # rfc2109 sec 4.3.2 says to reject them
113 # rfc2965 sec 3.3.2 says to reject them
114 # rfc6265 sec 4.1.2.3 says only that "public suffixes"
115 # should be rejected. They reference Mozilla's
116 # publicsuffix.org, which does not contain 'localhost'.
117 # However, empirically in early 2017 Firefox still rejects
118 # 'localhost'.
119 # Therefore, don't set a cookie domain if we're running on
120 # localhost to avoid this problem.
121 if (preg_match('/^\.localhost(:[0-9]+)?$/', $SessionDomain))
122 {
123 $SessionDomain = "";
124 }
125 session_set_cookie_params(self::$SessionLifetime, "/",
126 $SessionDomain, $SecureCookie, TRUE);
127
128 # attempt to start session
129 $SessionStarted = @session_start();
130
131 # if session start failed
132 if (!$SessionStarted)
133 {
134 # regenerate session ID and attempt to start session again
135 session_regenerate_id(TRUE);
136 session_start();
137 }
138
139 # bump up our cookie expiry time, so that it'll die
140 # $SessionLifetime from now, rather than $SessionLifetime
141 # from whenever we created it
142 setcookie(
143 session_name(), session_id(),
144 time() + self::$SessionLifetime, "/",
145 $SessionDomain, $SecureCookie, TRUE);
146 }
147
148 # set up our internal environment
149 $this->DB = new Database();
150
151 # set up our exception handler
152 set_exception_handler(array($this, "GlobalExceptionHandler"));
153
154 # load our settings from database
155 $this->LoadSettings();
156
157 # set PHP maximum execution time
158 ini_set("max_execution_time", $this->Settings["MaxExecTime"]);
159 set_time_limit($this->Settings["MaxExecTime"]);
160
161 # register events we handle internally
162 $this->RegisterEvent($this->PeriodicEvents);
163 $this->RegisterEvent($this->UIEvents);
164
165 # attempt to create SCSS cache directory if needed and it does not exist
166 if ($this->ScssSupportEnabled() && !is_dir(self::$ScssCacheDir))
167 { @mkdir(self::$ScssCacheDir, 0777, TRUE); }
168
169 # attempt to create minimized JS cache directory if needed and it does not exist
170 if ($this->UseMinimizedJavascript()
171 && $this->JavascriptMinimizationEnabled()
172 && !is_dir(self::$JSMinCacheDir))
173 {
174 @mkdir(self::$JSMinCacheDir, 0777, TRUE);
175 }
176 }
183 public function __destruct()
184 {
185 # if template location cache is flagged to be saved
186 if ($this->SaveTemplateLocationCache)
187 {
188 # write template location cache out and update cache expiration
189 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
190 ." SET TemplateLocationCache = '"
191 .addslashes(serialize(
192 $this->TemplateLocationCache))."',"
193 ." TemplateLocationCacheExpiration = '"
194 .date("Y-m-d H:i:s",
195 $this->TemplateLocationCacheExpiration)."'");
196 }
197
198 # if object location cache is flagged to be saved
199 if (self::$SaveObjectLocationCache)
200 {
201 # write object location cache out and update cache expiration
202 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
203 ." SET ObjectLocationCache = '"
204 .addslashes(serialize(
205 self::$ObjectLocationCache))."',"
206 ." ObjectLocationCacheExpiration = '"
207 .date("Y-m-d H:i:s",
208 self::$ObjectLocationCacheExpiration)."'");
209 }
210 }
218 public function GlobalExceptionHandler($Exception)
219 {
220 # display exception info
221 $Message = $Exception->getMessage();
222 $Location = str_replace(getcwd()."/", "",
223 $Exception->getFile()."[".$Exception->getLine()."]");
224 $Trace = preg_replace(":(#[0-9]+) ".getcwd()."/".":", "$1 ",
225 $Exception->getTraceAsString());
226 if (php_sapi_name() == "cli")
227 {
228 print "Uncaught Exception\n"
229 ."Message: ".$Message."\n"
230 ."Location: ".$Location."\n"
231 ."Trace: \n"
232 .$Trace."\n";
233 }
234 else
235 {
236 ?><table width="100%" cellpadding="5"
237 style="border: 2px solid #666666; background: #CCCCCC;
238 font-family: Courier New, Courier, monospace;
239 margin-top: 10px;"><tr><td>
240 <div style="color: #666666;">
241 <span style="font-size: 150%;">
242 <b>Uncaught Exception</b></span><br />
243 <b>Message:</b> <i><?= $Message ?></i><br />
244 <b>Location:</b> <i><?= $Location ?></i><br />
245 <b>Trace:</b> <blockquote><pre><?= $Trace ?></pre></blockquote>
246 </div>
247 </td></tr></table><?PHP
248 }
249
250 # log exception if not running from command line
251 if (php_sapi_name() !== "cli")
252 {
253 $TraceString = $Exception->getTraceAsString();
254 $TraceString = str_replace("\n", ", ", $TraceString);
255 $TraceString = preg_replace(":(#[0-9]+) ".getcwd()."/".":",
256 "$1 ", $TraceString);
257 $LogMsg = "Uncaught exception (".$Exception->getMessage().")"
258 ." at ".$Location."."
259 ." TRACE: ".$TraceString
260 ." URL: ".$this->FullUrl();
261 $this->LogError(self::LOGLVL_ERROR, $LogMsg);
262 }
263 }
284 public static function AddObjectDirectory(
285 $Dir, $NamespacePrefixes = array(), $Callback = NULL)
286 {
287 # check to make sure any supplied callback looks valid
288 if (!is_null($Callback) && !is_callable($Callback))
289 {
290 throw new InvalidArgumentException("Supplied callback (\""
291 .$Callback."\") is invalid.");
292 }
293
294 # make sure directory has trailing slash
295 $Dir = $Dir.((substr($Dir, -1) != "/") ? "/" : "");
296
297 # make sure namespaces is an array
298 if (!is_array($NamespacePrefixes))
299 {
300 if (is_string($NamespacePrefixes))
301 {
302 $NamespacePrefixes = [ $NamespacePrefixes ];
303 }
304 else
305 {
306 throw new InvalidArgumentException("Supplied namespace (\""
307 .$NamespacePrefixes."\") is invalid.");
308 }
309 }
310
311 # make sure namespace prefixes are in decreasing order of length
312 usort($NamespacePrefixes, function($A, $B)
313 {
314 return strlen($B) - strlen($A);
315 });
316
317 # add directory to directory list
318 self::$ObjectDirectories[$Dir] = array(
319 "NamespacePrefixes" => $NamespacePrefixes,
320 "Callback" => $Callback,
321 );
322 }
323
343 public function AddImageDirectories(
344 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
345 {
346 # add directories to existing image directory list
347 $this->ImageDirList = $this->AddToDirList(
348 $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
349 }
350
371 public function AddIncludeDirectories(
372 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
373 {
374 # add directories to existing image directory list
375 $this->IncludeDirList = $this->AddToDirList(
376 $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
377 }
378
398 public function AddInterfaceDirectories(
399 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
400 {
401 # add directories to existing image directory list
402 $this->InterfaceDirList = $this->AddToDirList(
403 $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
404
405 # cleared cached lists for user interfaces
406 self::$UserInterfaceListCache = array();
407 self::$UserInterfacePathsCache = array();
408 }
409
429 public function AddFunctionDirectories(
430 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
431 {
432 # add directories to existing image directory list
433 $this->FunctionDirList = $this->AddToDirList(
434 $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
435 }
436
442 public function SetBrowserDetectionFunc($DetectionFunc)
443 {
444 $this->BrowserDetectFunc = $DetectionFunc;
445 }
446
453 public function AddUnbufferedCallback($Callback, $Parameters=array())
454 {
455 if (is_callable($Callback))
456 {
457 $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
458 }
459 }
460
470 public function TemplateLocationCacheExpirationInterval(
471 $NewInterval = DB_NOVALUE, $Persistent = FALSE)
472 {
473 return $this->UpdateSetting(
474 "TemplateLocationCacheInterval", $NewInterval, $Persistent);
475 }
476
480 public function ClearTemplateLocationCache()
481 {
482 $this->TemplateLocationCache = array();
483 $this->SaveTemplateLocationCache = TRUE;
484 }
485
495 public function ObjectLocationCacheExpirationInterval(
496 $NewInterval = DB_NOVALUE, $Persistent = FALSE)
497 {
498 return $this->UpdateSetting(
499 "ObjectLocationCacheInterval", $NewInterval, $Persistent);
500 }
501
505 public function ClearObjectLocationCache()
506 {
507 self::$ObjectLocationCache = array();
508 self::$SaveObjectLocationCache = TRUE;
509 }
510
520 public function UrlFingerprintingEnabled($NewValue = DB_NOVALUE, $Persistent = FALSE)
521 {
522 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
523 }
524
535 public function ScssSupportEnabled($NewValue = DB_NOVALUE, $Persistent = FALSE)
536 {
537 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
538 }
539
551 public function GenerateCompactCss($NewValue = DB_NOVALUE, $Persistent = FALSE)
552 {
553 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
554 }
555
567 public function UseMinimizedJavascript($NewValue = DB_NOVALUE, $Persistent = FALSE)
568 {
569 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
570 }
571
583 public function JavascriptMinimizationEnabled(
584 $NewValue = DB_NOVALUE, $Persistent = FALSE)
585 {
586 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
587 }
588
602 public function RecordContextInCaseOfCrash(
603 $BacktraceOptions = 0, $BacktraceLimit = 0)
604 {
605 if (version_compare(PHP_VERSION, "5.4.0", ">="))
606 {
607 $this->SavedContext = debug_backtrace(
608 $BacktraceOptions, $BacktraceLimit);
609 }
610 else
611 {
612 $this->SavedContext = debug_backtrace($BacktraceOptions);
613 }
614 array_shift($this->SavedContext);
615 }
616
621 public function LoadPage($PageName)
622 {
623 # perform any clean URL rewriting
624 $PageName = $this->RewriteCleanUrls($PageName);
625
626 # sanitize incoming page name and save local copy
627 $PageName = preg_replace("/[^a-zA-Z0-9_.-]/", "", $PageName);
628 $this->PageName = $PageName;
629
630 # if page caching is turned on
631 if ($this->PageCacheEnabled())
632 {
633 # if we have a cached page
634 $CachedPage = $this->CheckForCachedPage($PageName);
635 if ($CachedPage !== NULL)
636 {
637 # set header to indicate cache hit was found
638 header("X-ScoutAF-Cache: HIT");
639
640 # display cached page and exit
641 print $CachedPage;
642 return;
643 }
644 else
645 {
646 # set header to indicate no cache hit was found
647 header("X-ScoutAF-Cache: MISS");
648 }
649 }
650
651 # buffer any output from includes or PHP file
652 ob_start();
653
654 # include any files needed to set up execution environment
655 $IncludeFileContext = array();
656 foreach ($this->EnvIncludes as $IncludeFile)
657 {
658 $IncludeFileContext = $this->FilterContext(self::CONTEXT_ENV,
659 self::IncludeFile($IncludeFile, $IncludeFileContext));
660 }
661
662 # signal page load
663 $this->SignalEvent("EVENT_PAGE_LOAD", array("PageName" => $PageName));
664
665 # signal PHP file load
666 $SignalResult = $this->SignalEvent("EVENT_PHP_FILE_LOAD", array(
667 "PageName" => $PageName));
668
669 # if signal handler returned new page name value
670 $NewPageName = $PageName;
671 if (($SignalResult["PageName"] != $PageName)
672 && strlen($SignalResult["PageName"]))
673 {
674 # if new page name value is page file
675 if (file_exists($SignalResult["PageName"]))
676 {
677 # use new value for PHP file name
678 $PageFile = $SignalResult["PageName"];
679 }
680 else
681 {
682 # use new value for page name
683 $NewPageName = $SignalResult["PageName"];
684 }
685
686 # update local copy of page name
687 $this->PageName = $NewPageName;
688 }
689
690 # if we do not already have a PHP file
691 if (!isset($PageFile))
692 {
693 # look for PHP file for page
694 $OurPageFile = "pages/".$NewPageName.".php";
695 $LocalPageFile = "local/pages/".$NewPageName.".php";
696 $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
697 : (file_exists($OurPageFile) ? $OurPageFile
698 : "pages/".$this->DefaultPage.".php");
699 }
700
701 # load PHP file
702 $IncludeFileContext = $this->FilterContext(self::CONTEXT_PAGE,
703 self::IncludeFile($PageFile, $IncludeFileContext));
704
705 # save buffered output to be displayed later after HTML file loads
706 $PageOutput = ob_get_contents();
707 ob_end_clean();
708
709 # signal PHP file load is complete
710 ob_start();
711 $Context["Variables"] = $IncludeFileContext;
712 $this->SignalEvent("EVENT_PHP_FILE_LOAD_COMPLETE",
713 array("PageName" => $PageName, "Context" => $Context));
714 $PageCompleteOutput = ob_get_contents();
715 ob_end_clean();
716
717 # set up for possible TSR (Terminate and Stay Resident :))
718 $ShouldTSR = $this->PrepForTSR();
719
720 # if PHP file indicated we should autorefresh to somewhere else
721 if (($this->JumpToPage) && ($this->JumpToPageDelay == 0))
722 {
723 if (!strlen(trim($PageOutput)))
724 {
725 # if client supports HTTP/1.1, use a 303 as it is most accurate
726 if ($_SERVER["SERVER_PROTOCOL"] == "HTTP/1.1")
727 {
728 header("HTTP/1.1 303 See Other");
729 header("Location: ".$this->JumpToPage);
730 }
731 else
732 {
733 # if the request was an HTTP/1.0 GET or HEAD, then
734 # use a 302 response code.
735
736 # NB: both RFC 2616 (HTTP/1.1) and RFC1945 (HTTP/1.0)
737 # explicitly prohibit automatic redirection via a 302
738 # if the request was not GET or HEAD.
739 if ($_SERVER["SERVER_PROTOCOL"] == "HTTP/1.0" &&
740 ($_SERVER["REQUEST_METHOD"] == "GET" ||
741 $_SERVER["REQUEST_METHOD"] == "HEAD") )
742 {
743 header("HTTP/1.0 302 Found");
744 header("Location: ".$this->JumpToPage);
745 }
746
747 # otherwise, fall back to a meta refresh
748 else
749 {
750 print '<html><head><meta http-equiv="refresh" '
751 .'content="0; URL='.$this->JumpToPage.'">'
752 .'</head><body></body></html>';
753 }
754 }
755 }
756 }
757 # else if HTML loading is not suppressed
758 elseif (!$this->SuppressHTML)
759 {
760 # set content-type to get rid of diacritic errors
761 header("Content-Type: text/html; charset="
762 .$this->HtmlCharset, TRUE);
763
764 # load common HTML file (defines common functions) if available
765 $CommonHtmlFile = $this->FindFile($this->IncludeDirList,
766 "Common", array("tpl", "html"));
767 if ($CommonHtmlFile)
768 {
769 $IncludeFileContext = $this->FilterContext(self::CONTEXT_COMMON,
770 self::IncludeFile($CommonHtmlFile, $IncludeFileContext));
771 }
772
773 # load UI functions
774 $this->LoadUIFunctions();
775
776 # begin buffering content
777 ob_start();
778
779 # signal HTML file load
780 $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD", array(
781 "PageName" => $PageName));
782
783 # if signal handler returned new page name value
784 $NewPageName = $PageName;
785 $PageContentFile = NULL;
786 if (($SignalResult["PageName"] != $PageName)
787 && strlen($SignalResult["PageName"]))
788 {
789 # if new page name value is HTML file
790 if (file_exists($SignalResult["PageName"]))
791 {
792 # use new value for HTML file name
793 $PageContentFile = $SignalResult["PageName"];
794 }
795 else
796 {
797 # use new value for page name
798 $NewPageName = $SignalResult["PageName"];
799 }
800 }
801
802 # load page content HTML file if available
803 if ($PageContentFile === NULL)
804 {
805 $PageContentFile = $this->FindFile(
806 $this->InterfaceDirList, $NewPageName,
807 array("tpl", "html"));
808 }
809 if ($PageContentFile)
810 {
811 $IncludeFileContext = $this->FilterContext(self::CONTEXT_INTERFACE,
812 self::IncludeFile($PageContentFile, $IncludeFileContext));
813 }
814 else
815 {
816 print "<h2>ERROR: No HTML/TPL template found"
817 ." for this page (".$NewPageName.").</h2>";
818 }
819
820 # signal HTML file load complete
821 $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD_COMPLETE");
822
823 # stop buffering and save output
824 $PageContentOutput = ob_get_contents();
825 ob_end_clean();
826
827 # if standard page start/end have not been suppressed
828 $PageStartOutput = "";
829 $PageEndOutput = "";
830 if (!$this->SuppressStdPageStartAndEnd)
831 {
832 # load page start HTML file if available
833 $PageStartFile = $this->FindFile($this->IncludeDirList, "Start",
834 array("tpl", "html"), array("StdPage", "StandardPage"));
835 if ($PageStartFile)
836 {
837 ob_start();
838 $IncludeFileContext = self::IncludeFile(
839 $PageStartFile, $IncludeFileContext);
840 $PageStartOutput = ob_get_contents();
841 ob_end_clean();
842 }
843 $IncludeFileContext = $this->FilterContext(
844 self::CONTEXT_START, $IncludeFileContext);
845
846 # load page end HTML file if available
847 $PageEndFile = $this->FindFile($this->IncludeDirList, "End",
848 array("tpl", "html"), array("StdPage", "StandardPage"));
849 if ($PageEndFile)
850 {
851 ob_start();
852 self::IncludeFile($PageEndFile, $IncludeFileContext);
853 $PageEndOutput = ob_get_contents();
854 ob_end_clean();
855 }
856 }
857
858 # clear include file context because it may be large and is no longer needed
859 unset($IncludeFileContext);
860
861 # if page auto-refresh requested
862 if ($this->JumpToPage)
863 {
864 # add auto-refresh tag to page
865 $this->AddMetaTag([
866 "http-equiv" => "refresh",
867 "content" => $this->JumpToPageDelay,
868 "url" => $this->JumpToPage,
869 ]);
870 }
871
872 # assemble full page
873 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
874
875 # get list of any required files not loaded
876 $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
877
878 # add file loading tags to page
879 $FullPageOutput = $this->AddFileTagsToPageOutput(
880 $FullPageOutput, $RequiredFiles);
881
882 # add any requested meta tags to page
883 $FullPageOutput = $this->AddMetaTagsToPageOutput($FullPageOutput);
884
885 # perform any regular expression replacements in output
886 $NewFullPageOutput = preg_replace($this->OutputModificationPatterns,
887 $this->OutputModificationReplacements, $FullPageOutput);
888
889 # check to make sure replacements didn't fail
890 $FullPageOutput = $this->CheckOutputModification(
891 $FullPageOutput, $NewFullPageOutput,
892 "regular expression replacements");
893
894 # for each registered output modification callback
895 foreach ($this->OutputModificationCallbacks as $Info)
896 {
897 # set up data for callback
898 $this->OutputModificationCallbackInfo = $Info;
899
900 # perform output modification
901 $NewFullPageOutput = preg_replace_callback($Info["SearchPattern"],
902 array($this, "OutputModificationCallbackShell"),
903 $FullPageOutput);
904
905 # check to make sure modification didn't fail
906 $ErrorInfo = "callback info: ".print_r($Info, TRUE);
907 $FullPageOutput = $this->CheckOutputModification(
908 $FullPageOutput, $NewFullPageOutput, $ErrorInfo);
909 }
910
911 # provide the opportunity to modify full page output
912 $SignalResult = $this->SignalEvent("EVENT_PAGE_OUTPUT_FILTER", array(
913 "PageOutput" => $FullPageOutput));
914 if (isset($SignalResult["PageOutput"])
915 && strlen(trim($SignalResult["PageOutput"])))
916 {
917 $FullPageOutput = $SignalResult["PageOutput"];
918 }
919
920 # if relative paths may not work because we were invoked via clean URL
921 if ($this->CleanUrlRewritePerformed || self::WasUrlRewritten())
922 {
923 # if using the <base> tag is okay
924 $BaseUrl = $this->BaseUrl();
925 if ($this->UseBaseTag)
926 {
927 # add <base> tag to header
928 $PageStartOutput = str_replace("<head>",
929 "<head><base href=\"".$BaseUrl."\" />",
930 $PageStartOutput);
931
932 # re-assemble full page with new header
933 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
934
935 # the absolute URL to the current page
936 $FullUrl = $BaseUrl . $this->GetPageLocation();
937
938 # make HREF attribute values with just a fragment ID
939 # absolute since they don't work with the <base> tag because
940 # they are relative to the current page/URL, not the site
941 # root
942 $NewFullPageOutput = preg_replace(
943 array("%href=\"(#[^:\" ]+)\"%i", "%href='(#[^:' ]+)'%i"),
944 array("href=\"".$FullUrl."$1\"", "href='".$FullUrl."$1'"),
945 $FullPageOutput);
946
947 # check to make sure HREF cleanup didn't fail
948 $FullPageOutput = $this->CheckOutputModification(
949 $FullPageOutput, $NewFullPageOutput,
950 "HREF cleanup");
951 }
952 else
953 {
954 # try to fix any relative paths throughout code
955 $SrcFileExtensions = "(js|css|gif|png|jpg|svg|ico)";
956 $RelativePathPatterns = array(
957 "%src=\"/?([^?*:;{}\\\\\" ]+)\.".$SrcFileExtensions."\"%i",
958 "%src='/?([^?*:;{}\\\\' ]+)\.".$SrcFileExtensions."'%i",
959 # don't rewrite HREF attributes that are just
960 # fragment IDs because they are relative to the
961 # current page/URL, not the site root
962 "%href=\"/?([^#][^:\" ]*)\"%i",
963 "%href='/?([^#][^:' ]*)'%i",
964 "%action=\"/?([^#][^:\" ]*)\"%i",
965 "%action='/?([^#][^:' ]*)'%i",
966 "%@import\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
967 "%@import\s+url\('/?([^:\" ]+)'\s*\)%i",
968 "%src:\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
969 "%src:\s+url\('/?([^:\" ]+)'\s*\)%i",
970 "%@import\s+\"/?([^:\" ]+)\"\s*%i",
971 "%@import\s+'/?([^:\" ]+)'\s*%i",
972 );
973 $RelativePathReplacements = array(
974 "src=\"".$BaseUrl."$1.$2\"",
975 "src=\"".$BaseUrl."$1.$2\"",
976 "href=\"".$BaseUrl."$1\"",
977 "href=\"".$BaseUrl."$1\"",
978 "action=\"".$BaseUrl."$1\"",
979 "action=\"".$BaseUrl."$1\"",
980 "@import url(\"".$BaseUrl."$1\")",
981 "@import url('".$BaseUrl."$1')",
982 "src: url(\"".$BaseUrl."$1\")",
983 "src: url('".$BaseUrl."$1')",
984 "@import \"".$BaseUrl."$1\"",
985 "@import '".$BaseUrl."$1'",
986 );
987 $NewFullPageOutput = preg_replace($RelativePathPatterns,
988 $RelativePathReplacements, $FullPageOutput);
989
990 # check to make sure relative path fixes didn't fail
991 $FullPageOutput = $this->CheckOutputModification(
992 $FullPageOutput, $NewFullPageOutput,
993 "relative path fixes");
994 }
995 }
996
997 # handle any necessary alternate domain rewriting
998 $FullPageOutput = $this->RewriteAlternateDomainUrls($FullPageOutput);
999
1000 # update page cache for this page
1001 $this->UpdatePageCache($PageName, $FullPageOutput);
1002
1003 # write out full page
1004 print $FullPageOutput;
1005 }
1006
1007 # run any post-processing routines
1008 foreach ($this->PostProcessingFuncs as $Func)
1009 {
1010 call_user_func_array($Func["FunctionName"], $Func["Arguments"]);
1011 }
1012
1013 # write out any output buffered from page code execution
1014 if (strlen($PageOutput))
1015 {
1016 if (!$this->SuppressHTML)
1017 {
1018 ?><table width="100%" cellpadding="5"
1019 style="border: 2px solid #666666; background: #CCCCCC;
1020 font-family: Courier New, Courier, monospace;
1021 margin-top: 10px;"><tr><td><?PHP
1022 }
1023 if ($this->JumpToPage)
1024 {
1025 ?><div style="color: #666666;"><span style="font-size: 150%;">
1026 <b>Page Jump Aborted</b></span>
1027 (because of error or other unexpected output)<br />
1028 <b>Jump Target:</b>
1029 <i><?PHP print($this->JumpToPage); ?></i></div><?PHP
1030 }
1031 print $PageOutput;
1032 if (!$this->SuppressHTML)
1033 {
1034 ?></td></tr></table><?PHP
1035 }
1036 }
1037
1038 # write out any output buffered from the page code execution complete signal
1039 if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
1040 {
1041 print $PageCompleteOutput;
1042 }
1043
1044 # log slow page loads
1045 if ($this->LogSlowPageLoads()
1046 && !$this->DoNotLogSlowPageLoad
1047 && ($this->GetElapsedExecutionTime()
1048 >= ($this->SlowPageLoadThreshold())))
1049 {
1050 $RemoteHost = gethostbyaddr($_SERVER["REMOTE_ADDR"]);
1051 if ($RemoteHost === FALSE)
1052 {
1053 $RemoteHost = $_SERVER["REMOTE_ADDR"];
1054 }
1055 elseif ($RemoteHost != $_SERVER["REMOTE_ADDR"])
1056 {
1057 $RemoteHost .= " (".$_SERVER["REMOTE_ADDR"].")";
1058 }
1059 $SlowPageLoadMsg = "Slow page load ("
1060 .intval($this->GetElapsedExecutionTime())."s) for "
1061 .$this->FullUrl()." from ".$RemoteHost;
1062 $this->LogMessage(self::LOGLVL_INFO, $SlowPageLoadMsg);
1063 }
1064
1065 # execute callbacks that should not have their output buffered
1066 foreach ($this->UnbufferedCallbacks as $Callback)
1067 {
1068 call_user_func_array($Callback[0], $Callback[1]);
1069 }
1070
1071 # log high memory usage
1072 if (function_exists("memory_get_peak_usage"))
1073 {
1074 $MemoryThreshold = ($this->HighMemoryUsageThreshold()
1075 * $this->GetPhpMemoryLimit()) / 100;
1076 if ($this->LogHighMemoryUsage()
1077 && (memory_get_peak_usage(TRUE) >= $MemoryThreshold))
1078 {
1079 $HighMemUsageMsg = "High peak memory usage ("
1080 .number_format(memory_get_peak_usage(TRUE)).") for "
1081 .$this->FullUrl()." from "
1082 .$_SERVER["REMOTE_ADDR"];
1083 $this->LogMessage(self::LOGLVL_INFO, $HighMemUsageMsg);
1084 }
1085 }
1086
1087 $this->UpdateLastUsedTimeForActiveSessions();
1088
1089 # terminate and stay resident (TSR!) if indicated and HTML has been output
1090 # (only TSR if HTML has been output because otherwise browsers will misbehave)
1091 if ($ShouldTSR) { $this->LaunchTSR(); }
1092 }
1093
1099 public function GetPageName()
1100 {
1101 return $this->PageName;
1102 }
1103
1109 public function GetPageLocation()
1110 {
1111 # retrieve current URL
1112 $Url = self::GetScriptUrl();
1113
1114 # remove the base path if present
1115 $BasePath = $this->Settings["BasePath"];
1116 if (stripos($Url, $BasePath) === 0)
1117 {
1118 $Url = substr($Url, strlen($BasePath));
1119 }
1120
1121 # if we're being accessed via an alternate domain,
1122 # add the appropriate prefix in
1123 if ($this->HtaccessSupport() &&
1124 self::$RootUrlOverride !== NULL)
1125 {
1126 $VHost = $_SERVER["SERVER_NAME"];
1127 if (isset($this->AlternateDomainPrefixes[$VHost]))
1128 {
1129 $ThisPrefix = $this->AlternateDomainPrefixes[$VHost];
1130 $Url = $ThisPrefix."/".$Url;
1131 }
1132 }
1133
1134 return $Url;
1135 }
1136
1142 public function GetPageUrl()
1143 {
1144 return self::BaseUrl() . $this->GetPageLocation();
1145 }
1146
1158 public function SetJumpToPage($Page, $Delay = 0, $IsLiteral = FALSE)
1159 {
1160 if (!is_null($Page)
1161 && (!$IsLiteral)
1162 && (strpos($Page, "?") === FALSE)
1163 && ((strpos($Page, "=") !== FALSE)
1164 || ((stripos($Page, ".php") === FALSE)
1165 && (stripos($Page, ".htm") === FALSE)
1166 && (strpos($Page, "/") === FALSE)))
1167 && (stripos($Page, "http://") !== 0)
1168 && (stripos($Page, "https://") !== 0))
1169 {
1170 $this->JumpToPage = self::BaseUrl() . "index.php?P=".$Page;
1171 }
1172 else
1173 {
1174 $this->JumpToPage = $Page;
1175 }
1176 $this->JumpToPageDelay = $Delay;
1177 }
1178
1183 public function JumpToPageIsSet()
1184 {
1185 return ($this->JumpToPage === NULL) ? FALSE : TRUE;
1186 }
1187
1197 public function HtmlCharset($NewSetting = NULL)
1198 {
1199 if ($NewSetting !== NULL) { $this->HtmlCharset = $NewSetting; }
1200 return $this->HtmlCharset;
1201 }
1202
1212 public function DoNotMinimizeFile($File)
1213 {
1214 if (!is_array($File)) { $File = array($File); }
1215 $this->DoNotMinimizeList = array_merge($this->DoNotMinimizeList, $File);
1216 }
1217
1228 public function UseBaseTag($NewValue = NULL)
1229 {
1230 if ($NewValue !== NULL) { $this->UseBaseTag = $NewValue ? TRUE : FALSE; }
1231 return $this->UseBaseTag;
1232 }
1233
1241 public function SuppressHTMLOutput($NewSetting = TRUE)
1242 {
1243 $this->SuppressHTML = $NewSetting;
1244 }
1245
1253 public function SuppressStandardPageStartAndEnd($NewSetting = TRUE)
1254 {
1255 $this->SuppressStdPageStartAndEnd = $NewSetting;
1256 }
1257
1263 public static function DefaultUserInterface($UIName = NULL)
1264 {
1265 if ($UIName !== NULL)
1266 {
1267 self::$DefaultUI = $UIName;
1268 }
1269 return self::$DefaultUI;
1270 }
1271
1278 public static function ActiveUserInterface($UIName = NULL)
1279 {
1280 if ($UIName !== NULL)
1281 {
1282 self::$ActiveUI = preg_replace("/^SPTUI--/", "", $UIName);
1283 }
1284 return self::$ActiveUI;
1285 }
1286
1297 public function GetUserInterfaces($FilterExp = NULL)
1298 {
1299 if (!isset(self::$UserInterfaceListCache[$FilterExp]))
1300 {
1301 # retrieve paths to user interface directories
1302 $Paths = $this->GetUserInterfacePaths($FilterExp);
1303
1304 # start out with an empty list
1305 self::$UserInterfaceListCache[$FilterExp] = array();
1306
1307 # for each possible UI directory
1308 foreach ($Paths as $CanonicalName => $Path)
1309 {
1310 # if name file available
1311 $LabelFile = $Path."/NAME";
1312 if (is_readable($LabelFile))
1313 {
1314 # read the UI name
1315 $Label = file_get_contents($LabelFile);
1316
1317 # if the UI name looks reasonable
1318 if (strlen(trim($Label)))
1319 {
1320 # use read name
1321 self::$UserInterfaceListCache[$FilterExp][$CanonicalName] =
1322 $Label;
1323 }
1324 }
1325
1326 # if we do not have a name yet
1327 if (!isset(self::$UserInterfaceListCache[$FilterExp][$CanonicalName]))
1328 {
1329 # use base directory for name
1330 self::$UserInterfaceListCache[$FilterExp][$CanonicalName] =
1331 basename($Path);
1332 }
1333 }
1334 }
1335
1336 # return list to caller
1337 return self::$UserInterfaceListCache[$FilterExp];
1338 }
1339
1348 public function GetUserInterfacePaths($FilterExp = NULL)
1349 {
1350 if (!isset(self::$UserInterfacePathsCache[$FilterExp]))
1351 {
1352 # extract possible UI directories from interface directory list
1353 $InterfaceDirs = array();
1354 foreach ($this->ExpandDirectoryList($this->InterfaceDirList) as $Dir)
1355 {
1356 $Matches = array();
1357 if (preg_match("#([a-zA-Z0-9/]*interface)/[a-zA-Z0-9%/]*#",
1358 $Dir, $Matches))
1359 {
1360 $Dir = $Matches[1];
1361 if (!in_array($Dir, $InterfaceDirs))
1362 {
1363 $InterfaceDirs[] = $Dir;
1364 }
1365 }
1366 }
1367
1368 # reverse order of interface directories so that the directory
1369 # returned is the base directory for the interface
1370 $InterfaceDirs = array_reverse($InterfaceDirs);
1371
1372 # start out with an empty list
1373 self::$UserInterfacePathsCache[$FilterExp] = array();
1374 $InterfacesFound = array();
1375
1376 # for each possible UI directory
1377 foreach ($InterfaceDirs as $InterfaceDir)
1378 {
1379 # check if the dir exists
1380 if (!is_dir($InterfaceDir))
1381 {
1382 continue;
1383 }
1384
1385 $Dir = dir($InterfaceDir);
1386
1387 # for each file in current directory
1388 while (($DirEntry = $Dir->read()) !== FALSE)
1389 {
1390 $InterfacePath = $InterfaceDir."/".$DirEntry;
1391
1392 # skip anything we have already found
1393 # or that doesn't have a name in the required format
1394 # or that isn't a directory
1395 # or that doesn't match the filter regex (if supplied)
1396 if (in_array($DirEntry, $InterfacesFound)
1397 || !preg_match('/^[a-zA-Z0-9]+$/', $DirEntry)
1398 || !is_dir($InterfacePath)
1399 || (($FilterExp !== NULL)
1400 && !preg_match($FilterExp, $InterfacePath)))
1401 {
1402 continue;
1403 }
1404
1405 # add interface to list
1406 self::$UserInterfacePathsCache[$FilterExp][$DirEntry] =
1407 $InterfacePath;
1408 $InterfacesFound[] = $DirEntry;
1409 }
1410
1411 $Dir->close();
1412 }
1413 }
1414
1415 # return list to caller
1416 return self::$UserInterfacePathsCache[$FilterExp];
1417 }
1418
1443 public function AddPostProcessingCall($FunctionName,
1444 &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
1445 &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
1446 &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
1447 {
1448 $FuncIndex = count($this->PostProcessingFuncs);
1449 $this->PostProcessingFuncs[$FuncIndex]["FunctionName"] = $FunctionName;
1450 $this->PostProcessingFuncs[$FuncIndex]["Arguments"] = array();
1451 $Index = 1;
1452 while (isset(${"Arg".$Index}) && (${"Arg".$Index} !== self::NOVALUE))
1453 {
1454 $this->PostProcessingFuncs[$FuncIndex]["Arguments"][$Index]
1455 =& ${"Arg".$Index};
1456 $Index++;
1457 }
1458 }
1459
1465 public function AddEnvInclude($FileName)
1466 {
1467 $this->EnvIncludes[] = $FileName;
1468 }
1469
1486 public function SetContextFilter($Context, $NewSetting)
1487 {
1488 if (($NewSetting === TRUE)
1489 || ($NewSetting === FALSE)
1490 || is_array($NewSetting))
1491 {
1492 $this->ContextFilters[$Context] = $NewSetting;
1493 }
1494 elseif (is_string($NewSetting))
1495 {
1496 $this->ContextFilters[$Context] = array($NewSetting);
1497 }
1498 else
1499 {
1500 throw new InvalidArgumentException(
1501 "Invalid setting (".$NewSetting.").");
1502 }
1503 }
1505 const CONTEXT_ENV = 1;
1507 const CONTEXT_PAGE = 2;
1509 const CONTEXT_COMMON = 3;
1511 const CONTEXT_INTERFACE = 4;
1513 const CONTEXT_START = 5;
1515 const CONTEXT_END = 6;
1516
1523 public function GUIFile($FileName)
1524 {
1525 # determine which location to search based on file suffix
1526 $FileType = $this->GetFileType($FileName);
1527 $DirList = ($FileType == self::FT_IMAGE)
1528 ? $this->ImageDirList : $this->IncludeDirList;
1529
1530 # if directed to use minimized JavaScript file
1531 if (($FileType == self::FT_JAVASCRIPT) && $this->UseMinimizedJavascript())
1532 {
1533 # look for minimized version of file
1534 $MinimizedFileName = substr_replace($FileName, ".min", -3, 0);
1535 $FoundFileName = $this->FindFile($DirList, $MinimizedFileName);
1536
1537 # if minimized file was not found
1538 if (is_null($FoundFileName))
1539 {
1540 # look for unminimized file
1541 $FoundFileName = $this->FindFile($DirList, $FileName);
1542
1543 # if unminimized file found
1544 if (!is_null($FoundFileName))
1545 {
1546 # if minimization enabled and supported
1547 if ($this->JavascriptMinimizationEnabled()
1548 && self::JsMinRewriteSupport())
1549 {
1550 # attempt to create minimized file
1551 $MinFileName = $this->MinimizeJavascriptFile(
1552 $FoundFileName);
1553
1554 # if minimization succeeded
1555 if ($MinFileName !== NULL)
1556 {
1557 # use minimized version
1558 $FoundFileName = $MinFileName;
1559
1560 # save file modification time if needed for fingerprinting
1561 if ($this->UrlFingerprintingEnabled())
1562 {
1563 $FileMTime = filemtime($FoundFileName);
1564 }
1565
1566 # strip off the cache location, allowing .htaccess
1567 # to handle that for us
1568 $FoundFileName = str_replace(
1569 self::$JSMinCacheDir."/", "", $FoundFileName);
1570 }
1571 }
1572 }
1573 }
1574 }
1575 # else if directed to use SCSS files
1576 elseif (($FileType == self::FT_CSS) && $this->ScssSupportEnabled())
1577 {
1578 # look for SCSS version of file
1579 $SourceFileName = preg_replace("/.css$/", ".scss", $FileName);
1580 $FoundSourceFileName = $this->FindFile($DirList, $SourceFileName);
1581
1582 # if SCSS file not found
1583 if ($FoundSourceFileName === NULL)
1584 {
1585 # look for CSS file
1586 $FoundFileName = $this->FindFile($DirList, $FileName);
1587 }
1588 else
1589 {
1590 # compile SCSS file (if updated) and return resulting CSS file
1591 $FoundFileName = $this->CompileScssFile($FoundSourceFileName);
1592
1593 # save file modification time if needed for fingerprinting
1594 if ($this->UrlFingerprintingEnabled())
1595 {
1596 $FileMTime = filemtime($FoundFileName);
1597 }
1598
1599 # strip off the cache location, allowing .htaccess to handle that for us
1600 if (self::ScssRewriteSupport())
1601 {
1602 $FoundFileName = str_replace(
1603 self::$ScssCacheDir."/", "", $FoundFileName);
1604 }
1605 }
1606 }
1607 # otherwise just search for the file
1608 else
1609 {
1610 $FoundFileName = $this->FindFile($DirList, $FileName);
1611 }
1612
1613 # add non-image files to list of found files (used for required files loading)
1614 if ($FileType != self::FT_IMAGE)
1615 { $this->FoundUIFiles[] = basename($FoundFileName); }
1616
1617 # if UI file fingerprinting is enabled and supported
1618 if ($this->UrlFingerprintingEnabled()
1619 && self::UrlFingerprintingRewriteSupport()
1620 && (isset($FileMTime) || file_exists($FoundFileName)))
1621 {
1622 # if file does not appear to be a server-side inclusion
1623 if (!preg_match('/\.(html|php)$/i', $FoundFileName))
1624 {
1625 # for each URL fingerprinting blacklist entry
1626 $OnBlacklist = FALSE;
1627 foreach ($this->UrlFingerprintBlacklist as $BlacklistEntry)
1628 {
1629 # if entry looks like a regular expression pattern
1630 if ($BlacklistEntry[0] == substr($BlacklistEntry, -1))
1631 {
1632 # check file name against regular expression
1633 if (preg_match($BlacklistEntry, $FoundFileName))
1634 {
1635 $OnBlacklist = TRUE;
1636 break;
1637 }
1638 }
1639 else
1640 {
1641 # check file name directly against entry
1642 if (basename($FoundFileName) == $BlacklistEntry)
1643 {
1644 $OnBlacklist = TRUE;
1645 break;
1646 }
1647 }
1648 }
1649
1650 # if file was not on blacklist
1651 if (!$OnBlacklist)
1652 {
1653 # get file modification time if not already retrieved
1654 if (!isset($FileMTime))
1655 {
1656 $FileMTime = filemtime($FoundFileName);
1657 }
1658
1659 # add timestamp fingerprint to file name
1660 $Fingerprint = sprintf("%06X",
1661 ($FileMTime % 0xFFFFFF));
1662 $FoundFileName = preg_replace("/^(.+)\.([a-z]+)$/",
1663 "$1.".$Fingerprint.".$2",
1664 $FoundFileName);
1665 }
1666 }
1667 }
1668
1669 # return file name to caller
1670 return $FoundFileName;
1671 }
1672
1681 public function PUIFile($FileName)
1682 {
1683 $FullFileName = $this->GUIFile($FileName);
1684 if ($FullFileName) { print($FullFileName); }
1685 }
1686
1701 public function IncludeUIFile($FileNames, $AdditionalAttributes = NULL)
1702 {
1703 # convert file name to array if necessary
1704 if (!is_array($FileNames)) { $FileNames = [ $FileNames ]; }
1705
1706 # pad additional attributes if supplied
1707 $AddAttribs = $AdditionalAttributes ? " ".$AdditionalAttributes : "";
1708
1709 # for each file
1710 foreach ($FileNames as $BaseFileName)
1711 {
1712 # retrieve full file name
1713 $FileName = $this->GUIFile($BaseFileName);
1714
1715 # if file was found
1716 if ($FileName)
1717 {
1718 # print appropriate tag
1719 print $this->GetUIFileLoadingTag($FileName, $AdditionalAttributes);
1720 }
1721
1722 # if we are not already loading an override file
1723 if (!preg_match("/-Override.(css|scss|js)$/", $BaseFileName))
1724 {
1725 # attempt to load override file if available
1726 $FileType = $this->GetFileType($BaseFileName);
1727 switch ($FileType)
1728 {
1729 case self::FT_CSS:
1730 $OverrideFileName = preg_replace(
1731 "/\.(css|scss)$/", "-Override.$1",
1732 $BaseFileName);
1733 $this->IncludeUIFile($OverrideFileName,
1734 $AdditionalAttributes);
1735 break;
1736
1737 case self::FT_JAVASCRIPT:
1738 $OverrideFileName = preg_replace(
1739 "/\.js$/", "-Override.js",
1740 $BaseFileName);
1741 $this->IncludeUIFile($OverrideFileName,
1742 $AdditionalAttributes);
1743 break;
1744 }
1745 }
1746 }
1747 }
1748
1755 public function DoNotUrlFingerprint($Pattern)
1756 {
1757 $this->UrlFingerprintBlacklist[] = $Pattern;
1758 }
1759
1771 public function RequireUIFile($FileNames, $Order = self::ORDER_MIDDLE)
1772 {
1773 # convert file names to array if necessary
1774 if (!is_array($FileNames)) { $FileNames = [ $FileNames ]; }
1775
1776 # add file names to list of required files
1777 foreach ($FileNames as $FileName)
1778 {
1779 $this->AdditionalRequiredUIFiles[$FileName] = $Order;
1780 }
1781 }
1782
1788 public static function GetFileType($FileName)
1789 {
1790 static $FileTypeCache;
1791 if (isset($FileTypeCache[$FileName]))
1792 {
1793 return $FileTypeCache[$FileName];
1794 }
1795
1796 $FileSuffix = strtolower(substr($FileName, -3));
1797 if ($FileSuffix == "css")
1798 {
1799 $FileTypeCache[$FileName] = self::FT_CSS;
1800 }
1801 elseif ($FileSuffix == ".js")
1802 {
1803 $FileTypeCache[$FileName] = self::FT_JAVASCRIPT;
1804 }
1805 elseif (($FileSuffix == "gif")
1806 || ($FileSuffix == "jpg")
1807 || ($FileSuffix == "png")
1808 || ($FileSuffix == "svg")
1809 || ($FileSuffix == "ico"))
1810 {
1811 $FileTypeCache[$FileName] = self::FT_IMAGE;
1812 }
1813 else
1814 {
1815 $FileTypeCache[$FileName] = self::FT_OTHER;
1816 }
1817
1818 return $FileTypeCache[$FileName];
1819 }
1821 const FT_OTHER = 0;
1823 const FT_CSS = 1;
1825 const FT_IMAGE = 2;
1827 const FT_JAVASCRIPT = 3;
1828
1837 public function LoadFunction($Callback)
1838 {
1839 # if specified function is not currently available
1840 if (!is_callable($Callback))
1841 {
1842 # if function info looks legal
1843 if (is_string($Callback) && strlen($Callback))
1844 {
1845 # start with function directory list
1846 $Locations = $this->FunctionDirList;
1847
1848 # add object directories to list
1849 $Locations = array_merge(
1850 $Locations, array_keys(self::$ObjectDirectories));
1851
1852 # look for function file
1853 $FunctionFileName = $this->FindFile($Locations, "F-".$Callback,
1854 array("php", "html"));
1855
1856 # if function file was found
1857 if ($FunctionFileName)
1858 {
1859 # load function file
1860 include_once($FunctionFileName);
1861 }
1862 else
1863 {
1864 # log error indicating function load failed
1865 $this->LogError(self::LOGLVL_ERROR, "Unable to load function"
1866 ." for callback \"".$Callback."\".");
1867 }
1868 }
1869 else
1870 {
1871 # log error indicating specified function info was bad
1872 $this->LogError(self::LOGLVL_ERROR, "Unloadable callback value"
1873 ." (".$Callback.")"
1874 ." passed to AF::LoadFunction() by "
1875 .StdLib::GetMyCaller().".");
1876 }
1877 }
1878
1879 # report to caller whether function load succeeded
1880 return is_callable($Callback);
1881 }
1882
1887 public function GetElapsedExecutionTime()
1888 {
1889 return microtime(TRUE) - $this->ExecutionStartTime;
1890 }
1891
1896 public function GetSecondsBeforeTimeout()
1897 {
1898 return $this->MaxExecutionTime() - $this->GetElapsedExecutionTime();
1899 }
1900
1905 public function AddMetaTag($Attribs)
1906 {
1907 # add new meta tag to list
1908 $this->MetaTags[] = $Attribs;
1909 }
1910
1919 public function AddMetaTagOnce($Attribs, $UniqueAttribs = NULL)
1920 {
1921 # add new meta tag to list
1922 $this->UniqueMetaTags[] = [
1923 "Attribs" => $Attribs,
1924 "UniqueAttribs" => $UniqueAttribs,
1925 ];
1926 }
1927
1928 /*@)*/ /* Application Framework */
1929
1930
1931 # ---- Page Caching ------------------------------------------------------
1932 /*@(*/
1934
1944 public function PageCacheEnabled(
1945 $NewValue = DB_NOVALUE, $Persistent = FALSE)
1946 {
1947 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
1948 }
1949
1959 public function PageCacheExpirationPeriod(
1960 $NewValue = DB_NOVALUE, $Persistent = FALSE)
1961 {
1962 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
1963 }
1964
1969 public function DoNotCacheCurrentPage()
1970 {
1971 $this->CacheCurrentPage = FALSE;
1972 }
1973
1980 public function AddPageCacheTag($Tag, $Pages = NULL)
1981 {
1982 # normalize tag
1983 $Tag = strtolower($Tag);
1984
1985 # if pages were supplied
1986 if ($Pages !== NULL)
1987 {
1988 # add pages to list for this tag
1989 if (isset($this->PageCacheTags[$Tag]))
1990 {
1991 $this->PageCacheTags[$Tag] = array_merge(
1992 $this->PageCacheTags[$Tag], $Pages);
1993 }
1994 else
1995 {
1996 $this->PageCacheTags[$Tag] = $Pages;
1997 }
1998 }
1999 else
2000 {
2001 # add current page to list for this tag
2002 $this->PageCacheTags[$Tag][] = "CURRENT";
2003 }
2004 }
2005
2011 public function ClearPageCacheForTag($Tag)
2012 {
2013 # get tag ID
2014 $TagId = $this->GetPageCacheTagId($Tag);
2015
2016 # delete pages and tag/page connections for specified tag
2017 $this->DB->Query("DELETE CP, CPTI"
2018 ." FROM AF_CachedPages CP, AF_CachedPageTagInts CPTI"
2019 ." WHERE CPTI.TagId = ".intval($TagId)
2020 ." AND CP.CacheId = CPTI.CacheId");
2021 }
2022
2026 public function ClearPageCache()
2027 {
2028 # clear all page cache tables
2029 $this->DB->Query("TRUNCATE TABLE AF_CachedPages");
2030 $this->DB->Query("TRUNCATE TABLE AF_CachedPageTags");
2031 $this->DB->Query("TRUNCATE TABLE AF_CachedPageTagInts");
2032 }
2033
2040 public function GetPageCacheInfo()
2041 {
2042 $Length = $this->DB->Query("SELECT COUNT(*) AS CacheLen"
2043 ." FROM AF_CachedPages", "CacheLen");
2044 $Oldest = $this->DB->Query("SELECT CachedAt FROM AF_CachedPages"
2045 ." ORDER BY CachedAt ASC LIMIT 1", "CachedAt");
2046 return array(
2047 "NumberOfEntries" => $Length,
2048 "OldestTimestamp" => strtotime($Oldest),
2049 );
2050 }
2051
2052 /*@)*/ /* Page Caching */
2053
2054
2055 # ---- Logging -----------------------------------------------------------
2056 /*@(*/
2058
2072 public function LogSlowPageLoads(
2073 $NewValue = DB_NOVALUE, $Persistent = FALSE)
2074 {
2075 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2076 }
2077
2088 public function SlowPageLoadThreshold(
2089 $NewValue = DB_NOVALUE, $Persistent = FALSE)
2090 {
2091 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2092 }
2093
2107 public function LogHighMemoryUsage(
2108 $NewValue = DB_NOVALUE, $Persistent = FALSE)
2109 {
2110 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2111 }
2112
2124 public function HighMemoryUsageThreshold(
2125 $NewValue = DB_NOVALUE, $Persistent = FALSE)
2126 {
2127 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2128 }
2129
2143 public function LogError($Level, $Msg)
2144 {
2145 # if error level is at or below current logging level
2146 if ($this->Settings["LoggingLevel"] >= $Level)
2147 {
2148 # attempt to log error message
2149 $Result = $this->LogMessage($Level, $Msg);
2150
2151 # if logging attempt failed and level indicated significant error
2152 if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
2153 {
2154 # throw exception about inability to log error
2155 static $AlreadyThrewException = FALSE;
2156 if (!$AlreadyThrewException)
2157 {
2158 $AlreadyThrewException = TRUE;
2159 throw new Exception("Unable to log error (".$Level.": ".$Msg
2160 .") to ".$this->LogFileName);
2161 }
2162 }
2163
2164 # report to caller whether message was logged
2165 return $Result;
2166 }
2167 else
2168 {
2169 # report to caller that message was not logged
2170 return FALSE;
2171 }
2172 }
2173
2185 public function LogMessage($Level, $Msg)
2186 {
2187 # if message level is at or below current logging level
2188 if ($this->Settings["LoggingLevel"] >= $Level)
2189 {
2190 # attempt to open log file
2191 $FHndl = @fopen($this->LogFileName, "a");
2192
2193 # if log file could not be open
2194 if ($FHndl === FALSE)
2195 {
2196 # report to caller that message was not logged
2197 return FALSE;
2198 }
2199 else
2200 {
2201 # format log entry
2202 $ErrorAbbrevs = array(
2203 self::LOGLVL_FATAL => "FTL",
2204 self::LOGLVL_ERROR => "ERR",
2205 self::LOGLVL_WARNING => "WRN",
2206 self::LOGLVL_INFO => "INF",
2207 self::LOGLVL_DEBUG => "DBG",
2208 self::LOGLVL_TRACE => "TRC",
2209 );
2210 $Msg = str_replace(array("\n", "\t", "\r"), " ", $Msg);
2211 $Msg = substr(trim($Msg), 0, self::LOGFILE_MAX_LINE_LENGTH);
2212 $LogEntry = date("Y-m-d H:i:s")
2213 ." ".($this->RunningInBackground ? "B" : "F")
2214 ." ".$ErrorAbbrevs[$Level]
2215 ." ".$Msg;
2216
2217 # write entry to log
2218 $Success = fwrite($FHndl, $LogEntry."\n");
2219
2220 # close log file
2221 fclose($FHndl);
2222
2223 # report to caller whether message was logged
2224 return ($Success === FALSE) ? FALSE : TRUE;
2225 }
2226 }
2227 else
2228 {
2229 # report to caller that message was not logged
2230 return FALSE;
2231 }
2232 }
2233
2258 public function LoggingLevel($NewValue = DB_NOVALUE, $Persistent = FALSE)
2259 {
2260 # constrain new level (if supplied) to within legal bounds
2261 if ($NewValue !== DB_NOVALUE)
2262 {
2263 $NewValue = max(min($NewValue, 6), 1);
2264 }
2265
2266 # set new logging level (if supplied) and return current level to caller
2267 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
2268 }
2269
2276 public function LogFile($NewValue = NULL)
2277 {
2278 if ($NewValue !== NULL) { $this->LogFileName = $NewValue; }
2279 return $this->LogFileName;
2280 }
2281
2291 public function GetLogEntries($Limit = 0)
2292 {
2293 # return no entries if there isn't a log file
2294 # or we can't read it or it's empty
2295 $LogFile = $this->LogFile();
2296 if (!is_readable($LogFile) || !filesize($LogFile))
2297 {
2298 return array();
2299 }
2300
2301 # if max number of entries specified
2302 if ($Limit > 0)
2303 {
2304 # load lines from file
2305 $FHandle = fopen($LogFile, "r");
2306 $FileSize = filesize($LogFile);
2307 $SeekPosition = max(0,
2308 ($FileSize - (self::LOGFILE_MAX_LINE_LENGTH
2309 * ($Limit + 1))));
2310 fseek($FHandle, $SeekPosition);
2311 $Block = fread($FHandle, ($FileSize - $SeekPosition));
2312 fclose($FHandle);
2313 $Lines = explode(PHP_EOL, $Block);
2314 array_pop($Lines);
2315
2316 # prune array back to requested number of entries
2317 $Lines = array_slice($Lines, (0 - $Limit));
2318 }
2319 else
2320 {
2321 # load all lines from log file
2322 $Lines = file($LogFile, FILE_IGNORE_NEW_LINES);
2323 if ($Lines === FALSE)
2324 {
2325 return array();
2326 }
2327 }
2328
2329 # reverse line order
2330 $Lines = array_reverse($Lines);
2331
2332 # for each log file line
2333 $Entries = array();
2334 foreach ($Lines as $Line)
2335 {
2336 # attempt to parse line into component parts
2337 $Pieces = explode(" ", $Line, 5);
2338 $Date = isset($Pieces[0]) ? $Pieces[0] : "";
2339 $Time = isset($Pieces[1]) ? $Pieces[1] : "";
2340 $Back = isset($Pieces[2]) ? $Pieces[2] : "";
2341 $Level = isset($Pieces[3]) ? $Pieces[3] : "";
2342 $Msg = isset($Pieces[4]) ? $Pieces[4] : "";
2343
2344 # skip line if it looks invalid
2345 $ErrorAbbrevs = array(
2346 "FTL" => self::LOGLVL_FATAL,
2347 "ERR" => self::LOGLVL_ERROR,
2348 "WRN" => self::LOGLVL_WARNING,
2349 "INF" => self::LOGLVL_INFO,
2350 "DBG" => self::LOGLVL_DEBUG,
2351 "TRC" => self::LOGLVL_TRACE,
2352 );
2353 if ((($Back != "F") && ($Back != "B"))
2354 || !array_key_exists($Level, $ErrorAbbrevs)
2355 || !strlen($Msg))
2356 {
2357 continue;
2358 }
2359
2360 # convert parts into appropriate values and add to entries
2361 $Entries[] = array(
2362 "Time" => strtotime($Date." ".$Time),
2363 "Background" => ($Back == "B") ? TRUE : FALSE,
2364 "Level" => $ErrorAbbrevs[$Level],
2365 "Message" => $Msg,
2366 );
2367 }
2368
2369 # return entries to caller
2370 return $Entries;
2371 }
2372
2377 const LOGLVL_TRACE = 6;
2382 const LOGLVL_DEBUG = 5;
2388 const LOGLVL_INFO = 4;
2393 const LOGLVL_WARNING = 3;
2399 const LOGLVL_ERROR = 2;
2404 const LOGLVL_FATAL = 1;
2405
2409 const LOGFILE_MAX_LINE_LENGTH = 2048;
2410
2411 /*@)*/ /* Logging */
2412
2413
2414 # ---- Event Handling ----------------------------------------------------
2415 /*@(*/
2417
2421 const EVENTTYPE_DEFAULT = 1;
2427 const EVENTTYPE_CHAIN = 2;
2433 const EVENTTYPE_FIRST = 3;
2441 const EVENTTYPE_NAMED = 4;
2442
2444 const ORDER_FIRST = 1;
2446 const ORDER_MIDDLE = 2;
2448 const ORDER_LAST = 3;
2449
2458 public function RegisterEvent($EventsOrEventName, $EventType = NULL)
2459 {
2460 # convert parameters to array if not already in that form
2461 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2462 : array($EventsOrEventName => $EventType);
2463
2464 # for each event
2465 foreach ($Events as $Name => $Type)
2466 {
2467 # store event information
2468 $this->RegisteredEvents[$Name]["Type"] = $Type;
2469 $this->RegisteredEvents[$Name]["Hooks"] = array();
2470 }
2471 }
2472
2479 public function IsRegisteredEvent($EventName)
2480 {
2481 return array_key_exists($EventName, $this->RegisteredEvents)
2482 ? TRUE : FALSE;
2483 }
2484
2491 public function IsHookedEvent($EventName)
2492 {
2493 # the event isn't hooked to if it isn't even registered
2494 if (!$this->IsRegisteredEvent($EventName))
2495 {
2496 return FALSE;
2497 }
2498
2499 # return TRUE if there is at least one callback hooked to the event
2500 return count($this->RegisteredEvents[$EventName]["Hooks"]) > 0;
2501 }
2502
2516 public function HookEvent(
2517 $EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
2518 {
2519 # convert parameters to array if not already in that form
2520 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2521 : array($EventsOrEventName => $Callback);
2522
2523 # for each event
2524 $Success = TRUE;
2525 foreach ($Events as $EventName => $EventCallback)
2526 {
2527 # if callback is valid
2528 if (is_callable($EventCallback))
2529 {
2530 # if this is a periodic event we process internally
2531 if (isset($this->PeriodicEvents[$EventName]))
2532 {
2533 # process event now
2534 $this->ProcessPeriodicEvent($EventName, $EventCallback);
2535 }
2536 # if specified event has been registered
2537 elseif (isset($this->RegisteredEvents[$EventName]))
2538 {
2539 # add callback for event
2540 $this->RegisteredEvents[$EventName]["Hooks"][]
2541 = array("Callback" => $EventCallback, "Order" => $Order);
2542
2543 # sort callbacks by order
2544 if (count($this->RegisteredEvents[$EventName]["Hooks"]) > 1)
2545 {
2546 usort($this->RegisteredEvents[$EventName]["Hooks"],
2547 function ($A, $B) {
2548 return StdLib::SortCompare(
2549 $A["Order"], $B["Order"]);
2550 });
2551 }
2552 }
2553 else
2554 {
2555 $Success = FALSE;
2556 }
2557 }
2558 else
2559 {
2560 $Success = FALSE;
2561 }
2562 }
2563
2564 # report to caller whether all callbacks were hooked
2565 return $Success;
2566 }
2567
2581 public function UnhookEvent(
2582 $EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
2583 {
2584 # convert parameters to array if not already in that form
2585 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2586 : array($EventsOrEventName => $Callback);
2587
2588 # for each event
2589 $UnhookCount = 0;
2590 foreach ($Events as $EventName => $EventCallback)
2591 {
2592 # if this event has been registered and hooked
2593 if (isset($this->RegisteredEvents[$EventName])
2594 && count($this->RegisteredEvents[$EventName]))
2595 {
2596 # if this callback has been hooked for this event
2597 $CallbackData = array("Callback" => $EventCallback, "Order" => $Order);
2598 if (in_array($CallbackData,
2599 $this->RegisteredEvents[$EventName]["Hooks"]))
2600 {
2601 # unhook callback
2602 $HookIndex = array_search($CallbackData,
2603 $this->RegisteredEvents[$EventName]["Hooks"]);
2604 unset($this->RegisteredEvents[$EventName]["Hooks"][$HookIndex]);
2605 $UnhookCount++;
2606 }
2607 }
2608 }
2609
2610 # report number of callbacks unhooked to caller
2611 return $UnhookCount;
2612 }
2613
2624 public function SignalEvent($EventName, $Parameters = NULL)
2625 {
2626 $ReturnValue = NULL;
2627
2628 # if event has been registered
2629 if (isset($this->RegisteredEvents[$EventName]))
2630 {
2631 # set up default return value (if not NULL)
2632 switch ($this->RegisteredEvents[$EventName]["Type"])
2633 {
2634 case self::EVENTTYPE_CHAIN:
2635 $ReturnValue = $Parameters;
2636 break;
2637
2638 case self::EVENTTYPE_NAMED:
2639 $ReturnValue = array();
2640 break;
2641 }
2642
2643 # for each callback for this event
2644 foreach ($this->RegisteredEvents[$EventName]["Hooks"] as $Hook)
2645 {
2646 # invoke callback
2647 $Callback = $Hook["Callback"];
2648 $Result = ($Parameters !== NULL)
2649 ? call_user_func_array($Callback, $Parameters)
2650 : call_user_func($Callback);
2651
2652 # process return value based on event type
2653 switch ($this->RegisteredEvents[$EventName]["Type"])
2654 {
2655 case self::EVENTTYPE_CHAIN:
2656 if ($Result !== NULL)
2657 {
2658 foreach ($Parameters as $Index => $Value)
2659 {
2660 if (array_key_exists($Index, $Result))
2661 {
2662 $Parameters[$Index] = $Result[$Index];
2663 }
2664 }
2665 $ReturnValue = $Parameters;
2666 }
2667 break;
2668
2669 case self::EVENTTYPE_FIRST:
2670 if ($Result !== NULL)
2671 {
2672 $ReturnValue = $Result;
2673 break 2;
2674 }
2675 break;
2676
2677 case self::EVENTTYPE_NAMED:
2678 $CallbackName = is_array($Callback)
2679 ? (is_object($Callback[0])
2680 ? get_class($Callback[0])
2681 : $Callback[0])."::".$Callback[1]
2682 : $Callback;
2683 $ReturnValue[$CallbackName] = $Result;
2684 break;
2685
2686 default:
2687 break;
2688 }
2689 }
2690 }
2691 else
2692 {
2693 $this->LogError(self::LOGLVL_WARNING,
2694 "Unregistered event (".$EventName.") signaled by "
2695 .StdLib::GetMyCaller().".");
2696 }
2697
2698 # return value if any to caller
2699 return $ReturnValue;
2700 }
2701
2707 public function IsStaticOnlyEvent($EventName)
2708 {
2709 return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
2710 }
2711
2722 public function EventWillNextRunAt($EventName, $Callback)
2723 {
2724 # if event is not a periodic event report failure to caller
2725 if (!array_key_exists($EventName, $this->EventPeriods)) { return FALSE; }
2726
2727 # retrieve last execution time for event if available
2728 $Signature = self::GetCallbackSignature($Callback);
2729 $LastRunTime = $this->DB->Query("SELECT LastRunAt FROM PeriodicEvents"
2730 ." WHERE Signature = '".addslashes($Signature)."'", "LastRunAt");
2731
2732 # if event was not found report failure to caller
2733 if ($LastRunTime === NULL) { return FALSE; }
2734
2735 # calculate next run time based on event period
2736 $NextRunTime = strtotime($LastRunTime) + $this->EventPeriods[$EventName];
2737
2738 # report next run time to caller
2739 return $NextRunTime;
2740 }
2741
2757 public function GetKnownPeriodicEvents()
2758 {
2759 # retrieve last execution times
2760 $this->DB->Query("SELECT * FROM PeriodicEvents");
2761 $LastRunTimes = $this->DB->FetchColumn("LastRunAt", "Signature");
2762
2763 # for each known event
2764 $Events = array();
2765 foreach ($this->KnownPeriodicEvents as $Signature => $Info)
2766 {
2767 # if last run time for event is available
2768 if (array_key_exists($Signature, $LastRunTimes))
2769 {
2770 # calculate next run time for event
2771 $LastRun = strtotime($LastRunTimes[$Signature]);
2772 $NextRun = $LastRun + $this->EventPeriods[$Info["Period"]];
2773 if ($Info["Period"] == "EVENT_PERIODIC") { $LastRun = FALSE; }
2774 }
2775 else
2776 {
2777 # set info to indicate run times are not known
2778 $LastRun = FALSE;
2779 $NextRun = FALSE;
2780 }
2781
2782 # add event info to list
2783 $Events[$Signature] = $Info;
2784 $Events[$Signature]["LastRun"] = $LastRun;
2785 $Events[$Signature]["NextRun"] = $NextRun;
2786 $Events[$Signature]["Parameters"] = NULL;
2787 }
2788
2789 # return list of known events to caller
2790 return $Events;
2791 }
2792
2799 public static function RunPeriodicEvent($EventName, $Callback, $Parameters)
2800 {
2801 static $DB;
2802 if (!isset($DB)) { $DB = new Database(); }
2803
2804 # run event
2805 $ReturnVal = call_user_func_array($Callback, $Parameters);
2806
2807 # if event is already in database
2808 $Signature = self::GetCallbackSignature($Callback);
2809 if ($DB->Query("SELECT COUNT(*) AS EventCount FROM PeriodicEvents"
2810 ." WHERE Signature = '".addslashes($Signature)."'", "EventCount"))
2811 {
2812 # update last run time for event
2813 $DB->Query("UPDATE PeriodicEvents SET LastRunAt = "
2814 .(($EventName == "EVENT_PERIODIC")
2815 ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
2816 : "NOW()")
2817 ." WHERE Signature = '".addslashes($Signature)."'");
2818 }
2819 else
2820 {
2821 # add last run time for event to database
2822 $DB->Query("INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES "
2823 ."('".addslashes($Signature)."', "
2824 .(($EventName == "EVENT_PERIODIC")
2825 ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
2826 : "NOW()").")");
2827 }
2828 }
2829
2830 /*@)*/ /* Event Handling */
2831
2832
2833 # ---- Task Management ---------------------------------------------------
2834 /*@(*/
2836
2838 const PRIORITY_HIGH = 1;
2840 const PRIORITY_MEDIUM = 2;
2842 const PRIORITY_LOW = 3;
2844 const PRIORITY_BACKGROUND = 4;
2845
2858 public function QueueTask($Callback, $Parameters = NULL,
2859 $Priority = self::PRIORITY_LOW, $Description = "")
2860 {
2861 # pack task info and write to database
2862 if ($Parameters === NULL) { $Parameters = array(); }
2863 $this->DB->Query("INSERT INTO TaskQueue"
2864 ." (Callback, Parameters, Priority, Description)"
2865 ." VALUES ('".addslashes(serialize($Callback))."', '"
2866 .addslashes(serialize($Parameters))."', ".intval($Priority).", '"
2867 .addslashes($Description)."')");
2868 }
2869
2887 public function QueueUniqueTask($Callback, $Parameters = NULL,
2888 $Priority = self::PRIORITY_LOW, $Description = "")
2889 {
2890 if ($this->TaskIsInQueue($Callback, $Parameters))
2891 {
2892 $QueryResult = $this->DB->Query("SELECT TaskId,Priority FROM TaskQueue"
2893 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2894 .($Parameters ? " AND Parameters = '"
2895 .addslashes(serialize($Parameters))."'" : ""));
2896 if ($QueryResult !== FALSE)
2897 {
2898 $Record = $this->DB->FetchRow();
2899 if ($Record["Priority"] > $Priority)
2900 {
2901 $this->DB->Query("UPDATE TaskQueue"
2902 ." SET Priority = ".intval($Priority)
2903 ." WHERE TaskId = ".intval($Record["TaskId"]));
2904 }
2905 }
2906 return FALSE;
2907 }
2908 else
2909 {
2910 $this->QueueTask($Callback, $Parameters, $Priority, $Description);
2911 return TRUE;
2912 }
2913 }
2914
2924 public function TaskIsInQueue($Callback, $Parameters = NULL)
2925 {
2926 $QueuedCount = $this->DB->Query(
2927 "SELECT COUNT(*) AS FoundCount FROM TaskQueue"
2928 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2929 .($Parameters ? " AND Parameters = '"
2930 .addslashes(serialize($Parameters))."'" : ""),
2931 "FoundCount");
2932 $RunningCount = $this->DB->Query(
2933 "SELECT COUNT(*) AS FoundCount FROM RunningTasks"
2934 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2935 .($Parameters ? " AND Parameters = '"
2936 .addslashes(serialize($Parameters))."'" : ""),
2937 "FoundCount");
2938 $FoundCount = $QueuedCount + $RunningCount;
2939 return ($FoundCount ? TRUE : FALSE);
2940 }
2941
2947 public function GetTaskQueueSize($Priority = NULL)
2948 {
2949 return $this->GetQueuedTaskCount(NULL, NULL, $Priority);
2950 }
2951
2959 public function GetQueuedTaskList($Count = 100, $Offset = 0)
2960 {
2961 return $this->GetTaskList("SELECT * FROM TaskQueue"
2962 ." ORDER BY Priority, TaskId ", $Count, $Offset);
2963 }
2964
2978 public function GetQueuedTaskCount($Callback = NULL,
2979 $Parameters = NULL, $Priority = NULL, $Description = NULL)
2980 {
2981 $Query = "SELECT COUNT(*) AS TaskCount FROM TaskQueue";
2982 $Sep = " WHERE";
2983 if ($Callback !== NULL)
2984 {
2985 $Query .= $Sep." Callback = '".addslashes(serialize($Callback))."'";
2986 $Sep = " AND";
2987 }
2988 if ($Parameters !== NULL)
2989 {
2990 $Query .= $Sep." Parameters = '".addslashes(serialize($Parameters))."'";
2991 $Sep = " AND";
2992 }
2993 if ($Priority !== NULL)
2994 {
2995 $Query .= $Sep." Priority = ".intval($Priority);
2996 $Sep = " AND";
2997 }
2998 if ($Description !== NULL)
2999 {
3000 $Query .= $Sep." Description = '".addslashes($Description)."'";
3001 }
3002 return $this->DB->Query($Query, "TaskCount");
3003 }
3004
3012 public function GetRunningTaskList($Count = 100, $Offset = 0)
3013 {
3014 return $this->GetTaskList("SELECT * FROM RunningTasks"
3015 ." WHERE StartedAt >= '".date("Y-m-d H:i:s",
3016 (time() - $this->MaxExecutionTime()))."'"
3017 ." ORDER BY StartedAt", $Count, $Offset);
3018 }
3019
3027 public function GetOrphanedTaskList($Count = 100, $Offset = 0)
3028 {
3029 return $this->GetTaskList("SELECT * FROM RunningTasks"
3030 ." WHERE StartedAt < '".date("Y-m-d H:i:s",
3031 (time() - $this->MaxExecutionTime()))."'"
3032 ." ORDER BY StartedAt", $Count, $Offset);
3033 }
3034
3039 public function GetOrphanedTaskCount()
3040 {
3041 return $this->DB->Query("SELECT COUNT(*) AS Count FROM RunningTasks"
3042 ." WHERE StartedAt < '".date("Y-m-d H:i:s",
3043 (time() - $this->MaxExecutionTime()))."'",
3044 "Count");
3045 }
3046
3052 public function ReQueueOrphanedTask($TaskId, $NewPriority = NULL)
3053 {
3054 $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
3055 $this->DB->Query("INSERT INTO TaskQueue"
3056 ." (Callback,Parameters,Priority,Description) "
3057 ."SELECT Callback, Parameters, Priority, Description"
3058 ." FROM RunningTasks WHERE TaskId = ".intval($TaskId));
3059 if ($NewPriority !== NULL)
3060 {
3061 $NewTaskId = $this->DB->LastInsertId();
3062 $this->DB->Query("UPDATE TaskQueue SET Priority = "
3063 .intval($NewPriority)
3064 ." WHERE TaskId = ".intval($NewTaskId));
3065 }
3066 $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
3067 $this->DB->Query("UNLOCK TABLES");
3068 }
3069
3076 public function RequeueCurrentTask($NewValue = TRUE)
3077 {
3078 $this->RequeueCurrentTask = $NewValue;
3079 }
3080
3086 public function DeleteTask($TaskId)
3087 {
3088 $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
3089 $TasksRemoved = $this->DB->NumRowsAffected();
3090 $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
3091 $TasksRemoved += $this->DB->NumRowsAffected();
3092 return $TasksRemoved;
3093 }
3094
3102 public function GetTask($TaskId)
3103 {
3104 # assume task will not be found
3105 $Task = NULL;
3106
3107 # look for task in task queue
3108 $this->DB->Query("SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
3109
3110 # if task was not found in queue
3111 if (!$this->DB->NumRowsSelected())
3112 {
3113 # look for task in running task list
3114 $this->DB->Query("SELECT * FROM RunningTasks WHERE TaskId = "
3115 .intval($TaskId));
3116 }
3117
3118 # if task was found
3119 if ($this->DB->NumRowsSelected())
3120 {
3121 # if task was periodic
3122 $Row = $this->DB->FetchRow();
3123 if ($Row["Callback"] ==
3124 serialize(array("ApplicationFramework", "RunPeriodicEvent")))
3125 {
3126 # unpack periodic task callback
3127 $WrappedCallback = unserialize($Row["Parameters"]);
3128 $Task["Callback"] = $WrappedCallback[1];
3129 $Task["Parameters"] = $WrappedCallback[2];
3130 }
3131 else
3132 {
3133 # unpack task callback and parameters
3134 $Task["Callback"] = unserialize($Row["Callback"]);
3135 $Task["Parameters"] = unserialize($Row["Parameters"]);
3136 }
3137 }
3138
3139 # return task to caller
3140 return $Task;
3141 }
3142
3153 public function TaskExecutionEnabled($NewValue = DB_NOVALUE, $Persistent = FALSE)
3154 {
3155 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
3156 }
3157
3166 public function MaxTasks($NewValue = DB_NOVALUE, $Persistent = FALSE)
3167 {
3168 return $this->UpdateSetting("MaxTasksRunning", $NewValue, $Persistent);
3169 }
3170
3178 public static function GetTaskCallbackSynopsis($TaskInfo)
3179 {
3180 # if task callback is function use function name
3181 $Callback = $TaskInfo["Callback"];
3182 $Name = "";
3183 if (!is_array($Callback))
3184 {
3185 $Name = $Callback;
3186 }
3187 else
3188 {
3189 # if task callback is object
3190 if (is_object($Callback[0]))
3191 {
3192 # if task callback is encapsulated ask encapsulation for name
3193 if (method_exists($Callback[0], "GetCallbackAsText"))
3194 {
3195 $Name = $Callback[0]->GetCallbackAsText();
3196 }
3197 # else assemble name from object
3198 else
3199 {
3200 $Name = get_class($Callback[0]) . "::" . $Callback[1];
3201 }
3202 }
3203 # else assemble name from supplied info
3204 else
3205 {
3206 $Name= $Callback[0] . "::" . $Callback[1];
3207 }
3208 }
3209
3210 # if parameter array was supplied
3211 $Parameters = $TaskInfo["Parameters"];
3212 $ParameterString = "";
3213 if (is_array($Parameters))
3214 {
3215 # assemble parameter string
3216 $Separator = "";
3217 foreach ($Parameters as $Parameter)
3218 {
3219 $ParameterString .= $Separator;
3220 if (is_int($Parameter) || is_float($Parameter))
3221 {
3222 $ParameterString .= $Parameter;
3223 }
3224 else if (is_string($Parameter))
3225 {
3226 $ParameterString .= "\"".htmlspecialchars($Parameter)."\"";
3227 }
3228 else if (is_array($Parameter))
3229 {
3230 $ParameterString .= "ARRAY";
3231 }
3232 else if (is_object($Parameter))
3233 {
3234 $ParameterString .= "OBJECT";
3235 }
3236 else if (is_null($Parameter))
3237 {
3238 $ParameterString .= "NULL";
3239 }
3240 else if (is_bool($Parameter))
3241 {
3242 $ParameterString .= $Parameter ? "TRUE" : "FALSE";
3243 }
3244 else if (is_resource($Parameter))
3245 {
3246 $ParameterString .= get_resource_type($Parameter);
3247 }
3248 else
3249 {
3250 $ParameterString .= "????";
3251 }
3252 $Separator = ", ";
3253 }
3254 }
3255
3256 # assemble name and parameters and return result to caller
3257 return $Name."(".$ParameterString.")";
3258 }
3259
3264 public function IsRunningInBackground()
3265 {
3266 return $this->RunningInBackground;
3267 }
3268
3274 public function GetCurrentBackgroundPriority()
3275 {
3276 return isset($this->RunningTask)
3277 ? $this->RunningTask["Priority"] : NULL;
3278 }
3279
3288 public function GetNextHigherBackgroundPriority($Priority = NULL)
3289 {
3290 if ($Priority === NULL)
3291 {
3292 $Priority = $this->GetCurrentBackgroundPriority();
3293 if ($Priority === NULL)
3294 {
3295 return NULL;
3296 }
3297 }
3298 return ($Priority > self::PRIORITY_HIGH)
3299 ? ($Priority - 1) : self::PRIORITY_HIGH;
3300 }
3301
3310 public function GetNextLowerBackgroundPriority($Priority = NULL)
3311 {
3312 if ($Priority === NULL)
3313 {
3314 $Priority = $this->GetCurrentBackgroundPriority();
3315 if ($Priority === NULL)
3316 {
3317 return NULL;
3318 }
3319 }
3320 return ($Priority < self::PRIORITY_BACKGROUND)
3321 ? ($Priority + 1) : self::PRIORITY_BACKGROUND;
3322 }
3323
3324 /*@)*/ /* Task Management */
3325
3326
3327 # ---- Clean URL Support -------------------------------------------------
3328 /*@(*/
3330
3357 public function AddCleanUrl($Pattern, $Page, $GetVars = NULL, $Template = NULL)
3358 {
3359 # save clean URL mapping parameters
3360 $this->CleanUrlMappings[] = array(
3361 "Pattern" => $Pattern,
3362 "Page" => $Page,
3363 "GetVars" => $GetVars,
3364 "AddedBy" => StdLib::GetCallerInfo(),
3365 );
3366
3367 # if replacement template specified
3368 if ($Template !== NULL)
3369 {
3370 # if GET parameters specified
3371 if (count($GetVars))
3372 {
3373 # retrieve all possible permutations of GET parameters
3374 $GetPerms = StdLib::ArrayPermutations(array_keys($GetVars));
3375
3376 # for each permutation of GET parameters
3377 foreach ($GetPerms as $VarPermutation)
3378 {
3379 # construct search pattern for permutation
3380 $SearchPattern = "/href=([\"'])index\\.php\\?P=".$Page;
3381 $GetVarSegment = "";
3382 foreach ($VarPermutation as $GetVar)
3383 {
3384 if (preg_match("%\\\$[0-9]+%", $GetVars[$GetVar]))
3385 {
3386 $GetVarSegment .= "&amp;".$GetVar."=((?:(?!\\1)[^&])+)";
3387 }
3388 else
3389 {
3390 $GetVarSegment .= "&amp;".$GetVar."=".$GetVars[$GetVar];
3391 }
3392 }
3393 $SearchPattern .= $GetVarSegment."\\1/i";
3394
3395 # if template is actually a callback
3396 if (is_callable($Template))
3397 {
3398 # add pattern to HTML output mod callbacks list
3399 $this->OutputModificationCallbacks[] = array(
3400 "Pattern" => $Pattern,
3401 "Page" => $Page,
3402 "SearchPattern" => $SearchPattern,
3403 "Callback" => $Template,
3404 );
3405 }
3406 else
3407 {
3408 # construct replacement string for permutation
3409 $Replacement = $Template;
3410 $Index = 2;
3411 foreach ($VarPermutation as $GetVar)
3412 {
3413 $Replacement = str_replace(
3414 "\$".$GetVar, "\$".$Index, $Replacement);
3415 $Index++;
3416 }
3417 $Replacement = "href=\"".$Replacement."\"";
3418
3419 # add pattern to HTML output modifications list
3420 $this->OutputModificationPatterns[] = $SearchPattern;
3421 $this->OutputModificationReplacements[] = $Replacement;
3422 }
3423 }
3424 }
3425 else
3426 {
3427 # construct search pattern
3428 $SearchPattern = "/href=\"index\\.php\\?P=".$Page."\"/i";
3429
3430 # if template is actually a callback
3431 if (is_callable($Template))
3432 {
3433 # add pattern to HTML output mod callbacks list
3434 $this->OutputModificationCallbacks[] = array(
3435 "Pattern" => $Pattern,
3436 "Page" => $Page,
3437 "SearchPattern" => $SearchPattern,
3438 "Callback" => $Template,
3439 );
3440 }
3441 else
3442 {
3443 # add simple pattern to HTML output modifications list
3444 $this->OutputModificationPatterns[] = $SearchPattern;
3445 $this->OutputModificationReplacements[] = "href=\"".$Template."\"";
3446 }
3447 }
3448 }
3449 }
3450
3456 public function CleanUrlIsMapped($Path)
3457 {
3458 foreach ($this->CleanUrlMappings as $Info)
3459 {
3460 if (preg_match($Info["Pattern"], $Path))
3461 {
3462 return TRUE;
3463 }
3464 }
3465 return FALSE;
3466 }
3467
3477 public function GetCleanUrlForPath($Path)
3478 {
3479 # the search patterns and callbacks require a specific format
3480 $Format = "href=\"".str_replace("&", "&amp;", $Path)."\"";
3481 $Search = $Format;
3482
3483 # perform any regular expression replacements on the search string
3484 $Search = preg_replace($this->OutputModificationPatterns,
3485 $this->OutputModificationReplacements, $Search);
3486
3487 # only run the callbacks if a replacement hasn't already been performed
3488 if ($Search == $Format)
3489 {
3490 # perform any callback replacements on the search string
3491 foreach ($this->OutputModificationCallbacks as $Info)
3492 {
3493 # make the information available to the callback
3494 $this->OutputModificationCallbackInfo = $Info;
3495
3496 # execute the callback
3497 $Search = preg_replace_callback($Info["SearchPattern"],
3498 array($this, "OutputModificationCallbackShell"),
3499 $Search);
3500 }
3501 }
3502
3503 # return the path untouched if no replacements were performed
3504 if ($Search == $Format)
3505 {
3506 return $Path;
3507 }
3508
3509 # remove the bits added to the search string to get it recognized by
3510 # the replacement expressions and callbacks
3511 $Result = substr($Search, 6, -1);
3512
3513 return $Result;
3514 }
3515
3522 public function GetUncleanUrlForPath($Path)
3523 {
3524 # for each clean URL mapping
3525 foreach ($this->CleanUrlMappings as $Info)
3526 {
3527 # if current path matches the clean URL pattern
3528 if (preg_match($Info["Pattern"], $Path, $Matches))
3529 {
3530 # the GET parameters for the URL, starting with the page name
3531 $GetVars = array("P" => $Info["Page"]);
3532
3533 # if additional $_GET variables specified for clean URL
3534 if ($Info["GetVars"] !== NULL)
3535 {
3536 # for each $_GET variable specified for clean URL
3537 foreach ($Info["GetVars"] as $VarName => $VarTemplate)
3538 {
3539 # start with template for variable value
3540 $Value = $VarTemplate;
3541
3542 # for each subpattern matched in current URL
3543 foreach ($Matches as $Index => $Match)
3544 {
3545 # if not first (whole) match
3546 if ($Index > 0)
3547 {
3548 # make any substitutions in template
3549 $Value = str_replace("$".$Index, $Match, $Value);
3550 }
3551 }
3552
3553 # add the GET variable
3554 $GetVars[$VarName] = $Value;
3555 }
3556 }
3557
3558 # return the unclean URL
3559 return "index.php?" . http_build_query($GetVars);
3560 }
3561 }
3562
3563 # return the path unchanged
3564 return $Path;
3565 }
3566
3572 public function GetCleanUrl()
3573 {
3574 return $this->GetCleanUrlForPath($this->GetUncleanUrl());
3575 }
3576
3581 public function GetUncleanUrl()
3582 {
3583 $GetVars = array("P" => $this->GetPageName()) + $_GET;
3584 return "index.php?" . http_build_query($GetVars);
3585 }
3586
3594 public function GetCleanUrlList()
3595 {
3596 return $this->CleanUrlMappings;
3597 }
3598
3611 public function AddPrefixForAlternateDomain($Domain, $Prefix)
3612 {
3613 $this->AlternateDomainPrefixes[$Domain] = $Prefix;
3614 }
3615
3616
3621 public function GetAlternateDomains()
3622 {
3623 return array_keys($this->AlternateDomainPrefixes);
3624 }
3625
3632 public function GetPrefixForAlternateDomain($Domain)
3633 {
3634 return isset($this->AlternateDomainPrefixes[$Domain]) ?
3635 $this->AlternateDomainPrefixes[$Domain] : NULL;
3636 }
3637
3638 /*@)*/ /* Clean URL Support */
3639
3640 # ---- Server Environment ------------------------------------------------
3641 /*@(*/
3643
3649 public static function SessionLifetime($NewValue = NULL)
3650 {
3651 if ($NewValue !== NULL)
3652 {
3653 self::$SessionLifetime = $NewValue;
3654 }
3655 return self::$SessionLifetime;
3656 }
3657
3663 public static function HtaccessSupport()
3664 {
3665 return isset($_SERVER["HTACCESS_SUPPORT"])
3666 || isset($_SERVER["REDIRECT_HTACCESS_SUPPORT"]);
3667 }
3668
3675 public static function UrlFingerprintingRewriteSupport()
3676 {
3677 return isset($_SERVER["URL_FINGERPRINTING_SUPPORT"])
3678 || isset($_SERVER["REDIRECT_URL_FINGERPRINTING_SUPPORT"]);
3679 }
3680
3687 public static function ScssRewriteSupport()
3688 {
3689 return isset($_SERVER["SCSS_REWRITE_SUPPORT"])
3690 || isset($_SERVER["REDIRECT_SCSS_REWRITE_SUPPORT"]);
3691 }
3692
3699 public static function JsMinRewriteSupport()
3700 {
3701 return isset($_SERVER["JSMIN_REWRITE_SUPPORT"])
3702 || isset($_SERVER["REDIRECT_JSMIN_REWRITE_SUPPORT"]);
3703 }
3704
3712 public static function RootUrl()
3713 {
3714 # return override value if one is set
3715 if (self::$RootUrlOverride !== NULL)
3716 {
3717 return self::$RootUrlOverride;
3718 }
3719
3720 # determine scheme name
3721 $Protocol = (isset($_SERVER["HTTPS"]) ? "https" : "http");
3722
3723 # if HTTP_HOST is preferred or SERVER_NAME points to localhost
3724 # and HTTP_HOST is set
3725 if ((self::$PreferHttpHost || ($_SERVER["SERVER_NAME"] == "127.0.0.1"))
3726 && isset($_SERVER["HTTP_HOST"]))
3727 {
3728 # use HTTP_HOST for domain name
3729 $DomainName = $_SERVER["HTTP_HOST"];
3730 }
3731 else
3732 {
3733 # use SERVER_NAME for domain name
3734 $DomainName = $_SERVER["SERVER_NAME"];
3735 }
3736
3737 # build URL root and return to caller
3738 return $Protocol."://".$DomainName;
3739 }
3740
3755 public static function RootUrlOverride($NewValue = self::NOVALUE)
3756 {
3757 if ($NewValue !== self::NOVALUE)
3758 {
3759 self::$RootUrlOverride = strlen(trim($NewValue)) ? $NewValue : NULL;
3760 }
3761 return self::$RootUrlOverride;
3762 }
3763
3773 public static function BaseUrl()
3774 {
3775 $BaseUrl = self::RootUrl().dirname($_SERVER["SCRIPT_NAME"]);
3776 if (substr($BaseUrl, -1) != "/") { $BaseUrl .= "/"; }
3777 return $BaseUrl;
3778 }
3779
3787 public static function FullUrl()
3788 {
3789 return self::RootUrl().$_SERVER["REQUEST_URI"];
3790 }
3791
3802 public static function PreferHttpHost($NewValue = NULL)
3803 {
3804 if ($NewValue !== NULL)
3805 {
3806 self::$PreferHttpHost = ($NewValue ? TRUE : FALSE);
3807 }
3808 return self::$PreferHttpHost;
3809 }
3810
3815 public static function BasePath()
3816 {
3817 $BasePath = dirname($_SERVER["SCRIPT_NAME"]);
3818
3819 if (substr($BasePath, -1) != "/")
3820 {
3821 $BasePath .= "/";
3822 }
3823
3824 return $BasePath;
3825 }
3826
3832 public static function GetScriptUrl()
3833 {
3834 if (array_key_exists("SCRIPT_URL", $_SERVER))
3835 {
3836 return $_SERVER["SCRIPT_URL"];
3837 }
3838 elseif (array_key_exists("REQUEST_URI", $_SERVER))
3839 {
3840 $Pieces = parse_url($_SERVER["REQUEST_URI"]);
3841 return isset($Pieces["path"]) ? $Pieces["path"] : NULL;
3842 }
3843 elseif (array_key_exists("REDIRECT_URL", $_SERVER))
3844 {
3845 return $_SERVER["REDIRECT_URL"];
3846 }
3847 else
3848 {
3849 return NULL;
3850 }
3851 }
3852
3861 public static function WasUrlRewritten($ScriptName="index.php")
3862 {
3863 # needed to get the path of the URL minus the query and fragment pieces
3864 $Components = parse_url(self::GetScriptUrl());
3865
3866 # if parsing was successful and a path is set
3867 if (is_array($Components) && isset($Components["path"]))
3868 {
3869 $BasePath = self::BasePath();
3870 $Path = $Components["path"];
3871
3872 # the URL was rewritten if the path isn't the base path, i.e., the
3873 # home page, and the file in the URL isn't the script generating the
3874 # page
3875 if ($BasePath != $Path && basename($Path) != $ScriptName)
3876 {
3877 return TRUE;
3878 }
3879 }
3880
3881 # the URL wasn't rewritten
3882 return FALSE;
3883 }
3884
3894 public static function ReachedViaAjax($NewSetting = NULL)
3895 {
3896 if ($NewSetting !== NULL)
3897 {
3898 self::$IsAjaxPageLoad = $NewSetting;
3899 }
3900
3901 if (isset(self::$IsAjaxPageLoad))
3902 {
3903 return self::$IsAjaxPageLoad;
3904 }
3905 elseif (isset($_SERVER["HTTP_X_REQUESTED_WITH"])
3906 && (strtolower($_SERVER["HTTP_X_REQUESTED_WITH"])
3907 == "xmlhttprequest"))
3908 {
3909 return TRUE;
3910 }
3911 else
3912 {
3913 return FALSE;
3914 }
3915 }
3916
3922 public static function GetFreeMemory()
3923 {
3924 return self::GetPhpMemoryLimit() - memory_get_usage(TRUE);
3925 }
3926
3932 public static function GetPhpMemoryLimit()
3933 {
3934 return self::ConvertPhpIniSizeToBytes(
3935 ini_get("memory_limit"));
3936 }
3937
3944 public static function GetPhpMaxUploadSize()
3945 {
3946 return min(
3947 self::ConvertPhpIniSizeToBytes(
3948 ini_get("post_max_size")),
3949 self::ConvertPhpIniSizeToBytes(
3950 ini_get("upload_max_filesize")));
3951 }
3952
3965 public function MaxExecutionTime($NewValue = DB_NOVALUE, $Persistent = FALSE)
3966 {
3967 if ($NewValue !== DB_NOVALUE)
3968 {
3969 $NewValue = max($NewValue, 5);
3970 ini_set("max_execution_time", $NewValue);
3971 set_time_limit($NewValue - $this->GetElapsedExecutionTime());
3972 $this->UpdateSetting("MaxExecTime", $NewValue, $Persistent);
3973 }
3974 return ini_get("max_execution_time");
3975 }
3976
3977 /*@)*/ /* Server Environment */
3978
3979
3980 # ---- Utility -----------------------------------------------------------
3981 /*@(*/
3983
3995 public function DownloadFile($FilePath, $FileName = NULL, $MimeType = NULL)
3996 {
3997 # check that file is readable
3998 if (!is_readable($FilePath))
3999 {
4000 return FALSE;
4001 }
4002
4003 # if file name was not supplied
4004 if ($FileName === NULL)
4005 {
4006 # extract file name from path
4007 $FileName = basename($FilePath);
4008 }
4009
4010 # if MIME type was not supplied
4011 if ($MimeType === NULL)
4012 {
4013 # attempt to determine MIME type
4014 $FInfoHandle = finfo_open(FILEINFO_MIME);
4015 if ($FInfoHandle)
4016 {
4017 $FInfoMime = finfo_file($FInfoHandle, $FilePath);
4018 finfo_close($FInfoHandle);
4019 if ($FInfoMime)
4020 {
4021 $MimeType = $FInfoMime;
4022 }
4023 }
4024
4025 # use default if unable to determine MIME type
4026 if ($MimeType === NULL)
4027 {
4028 $MimeType = "application/octet-stream";
4029 }
4030 }
4031
4032 # list of mime types where we allow the browser to decide on
4033 # how to display the item by omitting the Content-Disposition
4034 # header
4035 $InlineTypes = [
4036 "image/gif",
4037 "image/jpeg",
4038 "image/png",
4039 "application/pdf",
4040 ];
4041
4042 # set headers to download file
4043 header("Content-Type: ".$MimeType);
4044 header("Content-Length: ".filesize($FilePath));
4045 if ($this->CleanUrlRewritePerformed &&
4046 !in_array($MimeType, $InlineTypes))
4047 {
4048 header('Content-Disposition: attachment; filename="'.$FileName.'"');
4049 }
4050
4051 # make sure that apache does not attempt to compress file
4052 apache_setenv('no-gzip', '1');
4053
4054 # send file to user, but unbuffered to avoid memory issues
4055 $this->AddUnbufferedCallback(function ($File)
4056 {
4057 $BlockSize = 512000;
4058
4059 $Handle = @fopen($File, "rb");
4060 if ($Handle === FALSE)
4061 {
4062 return;
4063 }
4064
4065 # (close out session, making it read-only, so that session file
4066 # lock is released and others are not potentially hanging
4067 # waiting for it while the download completes)
4068 session_write_close();
4069
4070 while (!feof($Handle))
4071 {
4072 print fread($Handle, $BlockSize);
4073 flush();
4074 }
4075
4076 fclose($Handle);
4077 }, array($FilePath));
4078
4079 # prevent HTML output that might interfere with download
4080 $this->SuppressHTMLOutput();
4081
4082 # set flag to indicate not to log a slow page load in case client
4083 # connection delays PHP execution because of header
4084 $this->DoNotLogSlowPageLoad = TRUE;
4085
4086 # report no errors found to caller
4087 return TRUE;
4088 }
4089
4102 public function GetLock($LockName, $Wait = TRUE)
4103 {
4104 # assume we will not get a lock
4105 $GotLock = FALSE;
4106
4107 # clear out any stale locks
4108 static $CleanupHasBeenDone = FALSE;
4109 if (!$CleanupHasBeenDone)
4110 {
4111 # (margin for clearing stale locks is twice the known
4112 # maximum PHP execution time, because the max time
4113 # techinically does not include external operations
4114 # like database queries)
4115 $ClearLocksObtainedBefore = date(StdLib::SQL_DATE_FORMAT,
4116 (time() - ($this->MaxExecutionTime() * 2)));
4117 $this->DB->Query("DELETE FROM AF_Locks WHERE"
4118 ." ObtainedAt < '".$ClearLocksObtainedBefore."' AND"
4119 ." LockName = '".addslashes($LockName)."'");
4120 }
4121
4122 do
4123 {
4124 # lock database table so nobody else can try to get a lock
4125 $this->DB->Query("LOCK TABLES AF_Locks WRITE");
4126
4127 # look for lock with specified name
4128 $FoundCount = $this->DB->Query("SELECT COUNT(*) AS FoundCount"
4129 ." FROM AF_Locks WHERE LockName = '"
4130 .addslashes($LockName)."'", "FoundCount");
4131 $LockFound = ($FoundCount > 0) ? TRUE : FALSE;
4132
4133 # if lock found
4134 if ($LockFound)
4135 {
4136 # unlock database tables
4137 $this->DB->Query("UNLOCK TABLES");
4138
4139 # if blocking was requested
4140 if ($Wait)
4141 {
4142 # wait to give someone else a chance to release lock
4143 sleep(2);
4144 }
4145 }
4146 // @codingStandardsIgnoreStart
4147 // (because phpcs does not correctly handle do-while loops)
4148 # while lock was found and blocking was requested
4149 } while ($LockFound && $Wait);
4150 // @codingStandardsIgnoreEnd
4151
4152 # if lock was not found
4153 if (!$LockFound)
4154 {
4155 # get our lock
4156 $this->DB->Query("INSERT INTO AF_Locks (LockName) VALUES ('"
4157 .addslashes($LockName)."')");
4158 $GotLock = TRUE;
4159
4160 # unlock database tables
4161 $this->DB->Query("UNLOCK TABLES");
4162 }
4163
4164 # report to caller whether lock was obtained
4165 return $GotLock;
4166 }
4167
4175 public function ReleaseLock($LockName)
4176 {
4177 # release any existing locks
4178 $this->DB->Query("DELETE FROM AF_Locks WHERE LockName = '"
4179 .addslashes($LockName)."'");
4180
4181 # report to caller whether existing lock was released
4182 return $this->DB->NumRowsAffected() ? TRUE : FALSE;
4183 }
4184
4203 public function BeginAjaxResponse($ResponseType = "JSON", $CloseSession=TRUE)
4204 {
4205 switch ($ResponseType)
4206 {
4207 case "JSON":
4208 $this->SuppressHTMLOutput();
4209 header("Content-Type: application/json; charset="
4210 .$GLOBALS["G_SysConfig"]->DefaultCharacterSet(), TRUE);
4211 break;
4212 case "XML":
4213 $this->SuppressHTMLOutput();
4214 header("Content-Type: application/xml; charset="
4215 .$GLOBALS["G_SysConfig"]->DefaultCharacterSet(), TRUE);
4216 break;
4217 case "HTML":
4218 break;
4219 default:
4220 throw new Exception(
4221 "Unsupported response type: ".$ResponseType);
4222 }
4223
4224 $this->SetBrowserCacheExpirationTime(
4225 self::$DefaultBrowserCacheExpiration);
4226
4227 if ($CloseSession)
4228 {
4229 session_write_close();
4230 }
4231
4232 }
4233
4239 public function SetBrowserCacheExpirationTime($MaxAge)
4240 {
4241 # set headers to control caching
4242 header("Expires: ".gmdate("D, d M Y H:i:s \G\M\T", time()+$MaxAge));
4243 header("Cache-Control: private, max-age=".$MaxAge);
4244 header("Pragma:");
4245 }
4246
4253 public static function ConvertPhpIniSizeToBytes($Size)
4254 {
4255 $Str = strtoupper($Size);
4256
4257 # trim off 'B' suffix for KB/MB/GB
4258 if (substr($Str, -1) == "B")
4259 {
4260 $Str = substr($Str, 0, strlen($Str) - 1);
4261 }
4262
4263 # pull out the numeric part of our setting
4264 $Val = intval($Str);
4265
4266 # adjust it based on the units
4267 switch (substr($Str, -1))
4268 {
4269 case "G":
4270 $Val *= 1073741824;
4271 break;
4272
4273 case "M":
4274 $Val *= 1048576;
4275 break;
4276
4277 case "K":
4278 $Val *= 1024;
4279 break;
4280
4281 default:
4282 break;
4283 }
4284
4285 return $Val;
4286 }
4287
4293 public function SessionInUse($InUse = NULL)
4294 {
4295 if ($InUse !== NULL)
4296 {
4297 $this->SessionInUse = $InUse;
4298 }
4299
4300 return $this->SessionInUse;
4301 }
4302
4303 /*@)*/ /* Utility */
4304
4305
4306 # ---- Backward Compatibility --------------------------------------------
4307 /*@(*/
4309
4316 public function FindCommonTemplate($BaseName)
4317 {
4318 return $this->FindFile(
4319 $this->IncludeDirList, $BaseName, array("tpl", "html"));
4320 }
4321
4322 /*@)*/ /* Backward Compatibility */
4323
4324
4325 # ---- PRIVATE INTERFACE -------------------------------------------------
4326
4327 private $AdditionalRequiredUIFiles = array();
4328 private $AlternateDomainPrefixes = array();
4329 private $BackgroundTaskMemLeakLogThreshold = 10; # percentage of max mem
4330 private $BackgroundTaskMinFreeMemPercent = 25;
4331 private $BrowserDetectFunc;
4332 private $CacheCurrentPage = TRUE;
4333 private $CleanUrlMappings = array();
4334 private $CleanUrlRewritePerformed = FALSE;
4335 private $ContextFilters = array(
4336 self::CONTEXT_START => TRUE,
4337 self::CONTEXT_PAGE => array("H_"),
4338 self::CONTEXT_COMMON => array("H_"),
4339 );
4340 private $CssUrlFingerprintPath;
4341 private $DB;
4342 private $DefaultPage = "Home";
4343 private $DoNotMinimizeList = array();
4344 private $DoNotLogSlowPageLoad = FALSE;
4345 private $EnvIncludes = array();
4346 private $ExecutionStartTime;
4347 private $FoundUIFiles = array();
4348 private $HtmlCharset = "UTF-8";
4349 private $InterfaceSettings = array();
4350 private $JSMinimizerJavaScriptPackerAvailable = FALSE;
4351 private $JSMinimizerJShrinkAvailable = TRUE;
4352 private $JumpToPage = NULL;
4353 private $JumpToPageDelay = 0;
4354 private $LogFileName = "local/logs/site.log";
4355 private $MaxRunningTasksToTrack = 250;
4356 private $MetaTags = array();
4357 private $OutputModificationCallbackInfo;
4358 private $OutputModificationCallbacks = array();
4359 private $OutputModificationPatterns = array();
4360 private $OutputModificationReplacements = array();
4361 private $PageCacheTags = array();
4362 private $PageName;
4363 private $PostProcessingFuncs = array();
4364 private $RequeueCurrentTask;
4365 private $RunningInBackground = FALSE;
4366 private $RunningTask;
4367 private $SavedContext;
4368 private $SaveTemplateLocationCache = FALSE;
4369 private $SessionStorage;
4370 private $SessionGcProbability;
4371 private $Settings;
4372 private $SuppressHTML = FALSE;
4373 private $SuppressStdPageStartAndEnd = FALSE;
4374 private $TemplateLocationCache;
4375 private $TemplateLocationCacheInterval = 60; # in minutes
4376 private $TemplateLocationCacheExpiration;
4377 private $UnbufferedCallbacks = array();
4378 private $UniqueMetaTags = array();
4379 private $UrlFingerprintBlacklist = array();
4380 private $UseBaseTag = FALSE;
4381 private $SessionInUse = FALSE;
4382
4383 private static $ActiveUI = "default";
4384 private static $AppName = "ScoutAF";
4385 private static $DefaultBrowserCacheExpiration = 30; # in seconds
4386 private static $DefaultUI = "default";
4387 private static $IsAjaxPageLoad;
4388 private static $JSMinCacheDir = "local/data/caches/JSMin";
4389 private static $NamespaceDirectories = array();
4390 private static $ObjectDirectories = array();
4391 private static $ObjectLocationCache;
4392 private static $ObjectLocationCacheInterval = 60;
4393 private static $ObjectLocationCacheExpiration;
4394 private static $PreferHttpHost = FALSE;
4395 private static $RootUrlOverride = NULL;
4396 private static $SaveObjectLocationCache = FALSE;
4397 private static $ScssCacheDir = "local/data/caches/SCSS";
4398 private static $SessionLifetime = 1440; # in seconds
4399 private static $UserInterfaceListCache = array();
4400 private static $UserInterfacePathsCache = array();
4401
4402 # offset used to generate page cache tag IDs from numeric tags
4403 const PAGECACHETAGIDOFFSET = 100000;
4404
4405 # minimum expired session garbage collection probability
4406 const MIN_GC_PROBABILITY = 0.01;
4407
4412 private $NoTSR = FALSE;
4413
4414 private $RegisteredEvents = array();
4415 private $KnownPeriodicEvents = array();
4416 private $PeriodicEvents = array(
4417 "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
4418 "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
4419 "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
4420 "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
4421 "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
4422 );
4423 private $EventPeriods = array(
4424 "EVENT_HOURLY" => 3600,
4425 "EVENT_DAILY" => 86400,
4426 "EVENT_WEEKLY" => 604800,
4427 "EVENT_MONTHLY" => 2592000,
4428 "EVENT_PERIODIC" => 0,
4429 );
4430 private $UIEvents = array(
4431 "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
4432 "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
4433 "EVENT_PHP_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
4434 "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
4435 "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
4436 "EVENT_PAGE_OUTPUT_FILTER" => self::EVENTTYPE_CHAIN,
4437 );
4438
4443 private function LoadSettings()
4444 {
4445 # read settings in from database
4446 $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
4447 $this->Settings = $this->DB->FetchRow();
4448
4449 # if settings were not previously initialized
4450 if ($this->Settings === FALSE)
4451 {
4452 # initialize settings in database
4453 $this->DB->Query("INSERT INTO ApplicationFrameworkSettings"
4454 ." (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
4455
4456 # read new settings in from database
4457 $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
4458 $this->Settings = $this->DB->FetchRow();
4459
4460 # bail out if reloading new settings failed
4461 if ($this->Settings === FALSE)
4462 {
4463 throw new Exception(
4464 "Unable to load application framework settings.");
4465 }
4466 }
4467
4468 # if base path was not previously set or we appear to have moved
4469 if (!array_key_exists("BasePath", $this->Settings)
4470 || (!strlen($this->Settings["BasePath"]))
4471 || (!array_key_exists("BasePathCheck", $this->Settings))
4472 || (__FILE__ != $this->Settings["BasePathCheck"]))
4473 {
4474 # attempt to extract base path from Apache .htaccess file
4475 if (is_readable(".htaccess"))
4476 {
4477 $Lines = file(".htaccess");
4478 foreach ($Lines as $Line)
4479 {
4480 if (preg_match("/\\s*RewriteBase\\s+/", $Line))
4481 {
4482 $Pieces = preg_split(
4483 "/\\s+/", $Line, NULL, PREG_SPLIT_NO_EMPTY);
4484 $BasePath = $Pieces[1];
4485 }
4486 }
4487 }
4488
4489 # if base path was found
4490 if (isset($BasePath))
4491 {
4492 # save base path locally
4493 $this->Settings["BasePath"] = $BasePath;
4494
4495 # save base path to database
4496 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
4497 ." SET BasePath = '".addslashes($BasePath)."'"
4498 .", BasePathCheck = '".addslashes(__FILE__)."'");
4499 }
4500 }
4501
4502 # retrieve template location cache
4503 $this->TemplateLocationCache = unserialize(
4504 $this->Settings["TemplateLocationCache"]);
4505 $this->TemplateLocationCacheInterval =
4506 $this->Settings["TemplateLocationCacheInterval"];
4507 $this->TemplateLocationCacheExpiration =
4508 strtotime($this->Settings["TemplateLocationCacheExpiration"]);
4509
4510 # if template location cache looks invalid or has expired
4511 $CurrentTime = time();
4512 if (!is_array($this->TemplateLocationCache)
4513 || !count($this->TemplateLocationCache)
4514 || ($CurrentTime >= $this->TemplateLocationCacheExpiration))
4515 {
4516 # clear cache and reset cache expiration
4517 $this->TemplateLocationCache = array();
4518 $this->TemplateLocationCacheExpiration =
4519 $CurrentTime + ($this->TemplateLocationCacheInterval * 60);
4520 $this->SaveTemplateLocationCache = TRUE;
4521 }
4522
4523 # retrieve object location cache
4524 self::$ObjectLocationCache =
4525 unserialize($this->Settings["ObjectLocationCache"]);
4526 self::$ObjectLocationCacheInterval =
4527 $this->Settings["ObjectLocationCacheInterval"];
4528 self::$ObjectLocationCacheExpiration =
4529 strtotime($this->Settings["ObjectLocationCacheExpiration"]);
4530
4531 # if object location cache looks invalid or has expired
4532 if (!is_array(self::$ObjectLocationCache)
4533 || !count(self::$ObjectLocationCache)
4534 || ($CurrentTime >= self::$ObjectLocationCacheExpiration))
4535 {
4536 # clear cache and reset cache expiration
4537 self::$ObjectLocationCache = array();
4538 self::$ObjectLocationCacheExpiration =
4539 $CurrentTime + (self::$ObjectLocationCacheInterval * 60);
4540 self::$SaveObjectLocationCache = TRUE;
4541 }
4542 }
4543
4550 private function RewriteCleanUrls($PageName)
4551 {
4552 # if URL rewriting is supported by the server
4553 if ($this->HtaccessSupport())
4554 {
4555 # retrieve current URL and remove base path if present
4556 $Url = $this->GetPageLocation();
4557
4558 # for each clean URL mapping
4559 foreach ($this->CleanUrlMappings as $Info)
4560 {
4561 # if current URL matches clean URL pattern
4562 if (preg_match($Info["Pattern"], $Url, $Matches))
4563 {
4564 # set new page
4565 $PageName = $Info["Page"];
4566
4567 # if $_GET variables specified for clean URL
4568 if ($Info["GetVars"] !== NULL)
4569 {
4570 # for each $_GET variable specified for clean URL
4571 foreach ($Info["GetVars"] as $VarName => $VarTemplate)
4572 {
4573 # start with template for variable value
4574 $Value = $VarTemplate;
4575
4576 # for each subpattern matched in current URL
4577 foreach ($Matches as $Index => $Match)
4578 {
4579 # if not first (whole) match
4580 if ($Index > 0)
4581 {
4582 # make any substitutions in template
4583 $Value = str_replace("$".$Index, $Match, $Value);
4584 }
4585 }
4586
4587 # set $_GET variable
4588 $_GET[$VarName] = $Value;
4589 }
4590 }
4591
4592 # set flag indicating clean URL mapped
4593 $this->CleanUrlRewritePerformed = TRUE;
4594
4595 # stop looking for a mapping
4596 break;
4597 }
4598 }
4599 }
4600
4601 # return (possibly) updated page name to caller
4602 return $PageName;
4603 }
4604
4617 private function RewriteAlternateDomainUrls($Html)
4618 {
4619 # if we were loaded via an alternate domain, and we have a
4620 # RootUrlOverride configured to tell us which domain is the
4621 # primary, and if rewriting support is enabled, then we can
4622 # handle URL Rewriting
4623 if ($this->LoadedViaAlternateDomain() &&
4624 self::$RootUrlOverride !== NULL &&
4625 $this->HtaccessSupport())
4626 {
4627 # pull out the configured prefix for this domain
4628 $VHost = $_SERVER["SERVER_NAME"];
4629 $ThisPrefix = $this->AlternateDomainPrefixes[$VHost];
4630
4631 # get the URL for the primary domain, including the base path
4632 # (usually the part between the host name and the PHP file name)
4633 $RootUrl = $this->RootUrl().self::BasePath();
4634
4635 # and figure out what protcol we were using
4636 $Protocol = (isset($_SERVER["HTTPS"]) ? "https" : "http");
4637
4638 # NB: preg_replace iterates through the configured
4639 # search/replacement pairs, such that the second one
4640 # runs after the first and so on
4641
4642 # the first n-1 patterns below convert any relative
4643 # links in the generated HTML to absolute links using
4644 # our primary domain (e.g., for stylesheets, javascript,
4645 # images, etc)
4646
4647 # the nth pattern looks for links that live within the
4648 # path subtree specified by our configured prefix on
4649 # our primary domain, then replaces them with equivalent
4650 # links on our secondary domain
4651
4652 # for example, if our primary domain is
4653 # example.com/MySite and our secondary domain is
4654 # things.example.org/MySite with 'things' as the
4655 # configured prefix, then this last pattern will look
4656 # for example.com/MySite/things and replace it with
4657 # things.example.org/MySite
4658 $RelativePathPatterns = array(
4659 "%src=\"(?!http://|https://)%i",
4660 "%src='(?!http://|https://)%i",
4661 "%href=\"(?!http://|https://)%i",
4662 "%href='(?!http://|https://)%i",
4663 "%action=\"(?!http://|https://)%i",
4664 "%action='(?!http://|https://)%i",
4665 "%@import\s+url\(\"(?!http://|https://)%i",
4666 "%@import\s+url\('(?!http://|https://)%i",
4667 "%src:\s+url\(\"(?!http://|https://)%i",
4668 "%src:\s+url\('(?!http://|https://)%i",
4669 "%@import\s+\"(?!http://|https://)%i",
4670 "%@import\s+'(?!http://|https://)%i",
4671 "%".preg_quote($RootUrl.$ThisPrefix."/", "%")."%",
4672 );
4673 $RelativePathReplacements = array(
4674 "src=\"".$RootUrl,
4675 "src='".$RootUrl,
4676 "href=\"".$RootUrl,
4677 "href='".$RootUrl,
4678 "action=\"".$RootUrl,
4679 "action='".$RootUrl,
4680 "@import url(\"".$RootUrl,
4681 "@import url('".$RootUrl,
4682 "src: url(\"".$RootUrl,
4683 "src: url('".$RootUrl,
4684 "@import \"".$RootUrl,
4685 "@import '".$RootUrl,
4686 $Protocol."://".$VHost.self::BasePath(),
4687 );
4688
4689 $NewHtml = preg_replace(
4690 $RelativePathPatterns,
4691 $RelativePathReplacements,
4692 $Html);
4693
4694 # check to make sure relative path fixes didn't fail
4695 $Html = $this->CheckOutputModification(
4696 $Html, $NewHtml,
4697 "alternate domain substitutions");
4698 }
4699
4700 return $Html;
4701 }
4702
4707 private function LoadedViaAlternateDomain()
4708 {
4709 return (isset($_SERVER["SERVER_NAME"]) &&
4710 isset($this->AlternateDomainPrefixes[$_SERVER["SERVER_NAME"]])) ?
4711 TRUE : FALSE ;
4712 }
4713
4732 private function FindFile($DirectoryList, $BaseName,
4733 $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
4734 {
4735 # generate template cache index for this page
4736 $CacheIndex = md5(serialize($DirectoryList))
4737 .self::$DefaultUI.self::$ActiveUI.$BaseName;
4738
4739 # if caching is enabled and we have cached location
4740 if (($this->TemplateLocationCacheInterval > 0)
4741 && array_key_exists($CacheIndex,
4742 $this->TemplateLocationCache))
4743 {
4744 # use template location from cache
4745 $FoundFileName = $this->TemplateLocationCache[$CacheIndex];
4746 }
4747 else
4748 {
4749 # if suffixes specified and base name does not include suffix
4750 if ($PossibleSuffixes !== NULL
4751 && count($PossibleSuffixes)
4752 && !preg_match("/\.[a-zA-Z0-9]+$/", $BaseName))
4753 {
4754 # add versions of file names with suffixes to file name list
4755 $FileNames = array();
4756 foreach ($PossibleSuffixes as $Suffix)
4757 {
4758 $FileNames[] = $BaseName.".".$Suffix;
4759 }
4760 }
4761 else
4762 {
4763 # use base name as file name
4764 $FileNames = array($BaseName);
4765 }
4766
4767 # if prefixes specified
4768 if ($PossiblePrefixes !== NULL && count($PossiblePrefixes))
4769 {
4770 # add versions of file names with prefixes to file name list
4771 $NewFileNames = array();
4772 foreach ($FileNames as $FileName)
4773 {
4774 foreach ($PossiblePrefixes as $Prefix)
4775 {
4776 $NewFileNames[] = $Prefix.$FileName;
4777 }
4778 }
4779 $FileNames = $NewFileNames;
4780 }
4781
4782 # expand directory list to include variants
4783 $DirectoryList = $this->ExpandDirectoryList($DirectoryList);
4784
4785 # for each possible location
4786 $FoundFileName = NULL;
4787 foreach ($DirectoryList as $Dir)
4788 {
4789 # for each possible file name
4790 foreach ($FileNames as $File)
4791 {
4792 # if template is found at location
4793 if (file_exists($Dir.$File))
4794 {
4795 # save full template file name and stop looking
4796 $FoundFileName = $Dir.$File;
4797 break 2;
4798 }
4799 }
4800 }
4801
4802 # save location in cache
4803 $this->TemplateLocationCache[$CacheIndex]
4804 = $FoundFileName;
4805
4806 # set flag indicating that cache should be saved
4807 $this->SaveTemplateLocationCache = TRUE;
4808 }
4809
4810 # return full template file name to caller
4811 return $FoundFileName;
4812 }
4813
4820 private function ExpandDirectoryList($DirList)
4821 {
4822 # generate lookup for supplied list
4823 $ExpandedListKey = md5(serialize($DirList)
4824 .self::$DefaultUI.self::$ActiveUI);
4825
4826 # if we already have expanded version of supplied list
4827 if (isset($this->ExpandedDirectoryLists[$ExpandedListKey]))
4828 {
4829 # return expanded version to caller
4830 return $this->ExpandedDirectoryLists[$ExpandedListKey];
4831 }
4832
4833 # for each directory in list
4834 $ExpDirList = array();
4835 foreach ($DirList as $Dir)
4836 {
4837 # if directory includes substitution keyword
4838 if ((strpos($Dir, "%DEFAULTUI%") !== FALSE)
4839 || (strpos($Dir, "%ACTIVEUI%") !== FALSE))
4840 {
4841 # start with empty new list segment
4842 $ExpDirListSegment = array();
4843
4844 # use default values for initial parent
4845 $ParentInterface = array(self::$ActiveUI, self::$DefaultUI);
4846
4847 do
4848 {
4849 # substitute in for keyword on parent
4850 $CurrDir = str_replace(array("%ACTIVEUI%", "%DEFAULTUI%"),
4851 $ParentInterface, $Dir);
4852
4853 # add local version of parent directory to new list segment
4854 $ExpDirListSegment[] = "local/".$CurrDir;
4855
4856 # add parent directory to new list segment
4857 $ExpDirListSegment[] = $CurrDir;
4858
4859 # look for new parent interface
4860 $ParentInterface = $this->GetInterfaceSetting(
4861 $CurrDir, "ParentInterface");
4862
4863 # repeat if parent is available
4864 } while (strlen($ParentInterface));
4865
4866 # add new list segment to expanded list
4867 $ExpDirList = array_merge($ExpDirList, $ExpDirListSegment);
4868 }
4869 else
4870 {
4871 # add local version of directory to expanded list
4872 $ExpDirList[] = "local/".$Dir;
4873
4874 # add directory to expanded list
4875 $ExpDirList[] = $Dir;
4876 }
4877 }
4878
4879 # return expanded version to caller
4880 $this->ExpandedDirectoryLists[$ExpandedListKey] = $ExpDirList;
4881 return $this->ExpandedDirectoryLists[$ExpandedListKey];
4882 }
4883
4892 private function GetInterfaceSetting($InterfaceDir, $SettingName = NULL)
4893 {
4894 # extract canonical interface name and base interface directory
4895 preg_match("%(.*interface/)([^/]+)%", $InterfaceDir, $Matches);
4896 $InterfaceDir = (count($Matches) > 2)
4897 ? $Matches[1].$Matches[2] : $InterfaceDir;
4898 $InterfaceName = (count($Matches) > 2)
4899 ? $Matches[2] : "UNKNOWN";
4900
4901 # if we do not have settings for interface
4902 if (!isset($this->InterfaceSettings[$InterfaceName]))
4903 {
4904 # load default values for settings
4905 $this->InterfaceSettings[$InterfaceName] = array(
4906 "Source" => "",
4907 );
4908 }
4909
4910 # if directory takes precedence over existing settings source
4911 # ("takes precendence" == is more local == longer directory length)
4912 if (strlen($InterfaceDir)
4913 > strlen($this->InterfaceSettings[$InterfaceName]["Source"]))
4914 {
4915 # if settings file exists in directory
4916 $SettingsFile = $InterfaceDir."/interface.ini";
4917 if (is_readable($SettingsFile))
4918 {
4919 # read in values from file
4920 $NewSettings = parse_ini_file($SettingsFile);
4921
4922 # merge in values with existing settings
4923 $this->InterfaceSettings[$InterfaceName] = array_merge(
4924 $this->InterfaceSettings[$InterfaceName], $NewSettings);
4925
4926 # save new source of settings
4927 $this->InterfaceSettings[$InterfaceName]["Source"] = $InterfaceDir;
4928 }
4929 }
4930
4931 # return interface settings to caller
4932 return $SettingName
4933 ? (isset($this->InterfaceSettings[$InterfaceName][$SettingName])
4934 ? $this->InterfaceSettings[$InterfaceName][$SettingName]
4935 : NULL)
4936 : $this->InterfaceSettings[$InterfaceName];
4937 }
4938
4947 private function CompileScssFile($SrcFile)
4948 {
4949 # build path to CSS file
4950 $DstFile = self::$ScssCacheDir."/".dirname($SrcFile)
4951 ."/".basename($SrcFile);
4952 $DstFile = substr_replace($DstFile, "css", -4);
4953
4954 # if SCSS file is newer than CSS file
4955 if (!file_exists($DstFile)
4956 || (filemtime($SrcFile) > filemtime($DstFile)))
4957 {
4958 # attempt to create CSS cache subdirectory if not present
4959 if (!is_dir(dirname($DstFile)))
4960 {
4961 @mkdir(dirname($DstFile), 0777, TRUE);
4962 }
4963
4964 # if CSS cache directory and CSS file path appear writable
4965 static $CacheDirIsWritable;
4966 if (!isset($CacheDirIsWritable))
4967 { $CacheDirIsWritable = is_writable(self::$ScssCacheDir); }
4968 if (is_writable($DstFile)
4969 || (!file_exists($DstFile) && $CacheDirIsWritable))
4970 {
4971 # load SCSS and compile to CSS
4972 $ScssCode = file_get_contents($SrcFile);
4973 $ScssCompiler = new scssc();
4974 $ScssCompiler->setFormatter($this->GenerateCompactCss()
4975 ? "scss_formatter_compressed" : "scss_formatter");
4976 try
4977 {
4978 $CssCode = $ScssCompiler->compile($ScssCode);
4979
4980 # add fingerprinting for URLs in CSS
4981 $this->CssUrlFingerprintPath = dirname($SrcFile);
4982 $CssCode = preg_replace_callback(
4983 "/url\((['\"]*)(.+)\.([a-z]+)(['\"]*)\)/",
4984 array($this, "CssUrlFingerprintInsertion"),
4985 $CssCode);
4986
4987 # strip out comments from CSS (if requested)
4988 if ($this->GenerateCompactCss())
4989 {
4990 $CssCode = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!',
4991 '', $CssCode);
4992 }
4993
4994 # write out CSS file
4995 file_put_contents($DstFile, $CssCode);
4996 }
4997 catch (Exception $Ex)
4998 {
4999 $this->LogError(self::LOGLVL_ERROR,
5000 "Error compiling SCSS file ".$SrcFile.": "
5001 .$Ex->getMessage());
5002 $DstFile = NULL;
5003 }
5004 }
5005 else
5006 {
5007 # log error and set CSS file path to indicate failure
5008 $this->LogError(self::LOGLVL_ERROR,
5009 "Unable to write out CSS file (compiled from SCSS) to "
5010 .$DstFile);
5011 $DstFile = NULL;
5012 }
5013 }
5014
5015 # return CSS file path to caller
5016 return $DstFile;
5017 }
5018
5026 private function MinimizeJavascriptFile($SrcFile)
5027 {
5028 # bail out if file is on exclusion list
5029 foreach ($this->DoNotMinimizeList as $DNMFile)
5030 {
5031 if (($SrcFile == $DNMFile) || (basename($SrcFile) == $DNMFile))
5032 {
5033 return NULL;
5034 }
5035 }
5036
5037 # build path to minimized file
5038 $DstFile = self::$JSMinCacheDir."/".dirname($SrcFile)
5039 ."/".basename($SrcFile);
5040 $DstFile = substr_replace($DstFile, ".min", -3, 0);
5041
5042 # if original file is newer than minimized file
5043 if (!file_exists($DstFile)
5044 || (filemtime($SrcFile) > filemtime($DstFile)))
5045 {
5046 # attempt to create cache subdirectory if not present
5047 if (!is_dir(dirname($DstFile)))
5048 {
5049 @mkdir(dirname($DstFile), 0777, TRUE);
5050 }
5051
5052 # if cache directory and minimized file path appear writable
5053 static $CacheDirIsWritable;
5054 if (!isset($CacheDirIsWritable))
5055 { $CacheDirIsWritable = is_writable(self::$JSMinCacheDir); }
5056 if (is_writable($DstFile)
5057 || (!file_exists($DstFile) && $CacheDirIsWritable))
5058 {
5059 # load JavaScript code
5060 $Code = file_get_contents($SrcFile);
5061
5062 # decide which minimizer to use
5063 if ($this->JSMinimizerJavaScriptPackerAvailable
5064 && $this->JSMinimizerJShrinkAvailable)
5065 {
5066 $Minimizer = (strlen($Code) < 5000)
5067 ? "JShrink" : "JavaScriptPacker";
5068 }
5069 elseif ($this->JSMinimizerJShrinkAvailable)
5070 {
5071 $Minimizer = "JShrink";
5072 }
5073 else
5074 {
5075 $Minimizer = "NONE";
5076 }
5077
5078 # minimize code
5079 switch ($Minimizer)
5080 {
5081 case "JavaScriptMinimizer":
5082 $Packer = new JavaScriptPacker($Code, "Normal");
5083 $MinimizedCode = $Packer->pack();
5084 break;
5085
5086 case "JShrink":
5087 try
5088 {
5089 $MinimizedCode = \JShrink\Minifier::minify($Code);
5090 }
5091 catch (Exception $Exception)
5092 {
5093 unset($MinimizedCode);
5094 $MinimizeError = $Exception->getMessage();
5095 }
5096 break;
5097 }
5098
5099 # if minimization succeeded
5100 if (isset($MinimizedCode))
5101 {
5102 # write out minimized file
5103 file_put_contents($DstFile, $MinimizedCode);
5104 }
5105 else
5106 {
5107 # log error and set destination file path to indicate failure
5108 $ErrMsg = "Unable to minimize JavaScript file ".$SrcFile;
5109 if (isset($MinimizeError))
5110 {
5111 $ErrMsg .= " (".$MinimizeError.")";
5112 }
5113 $this->LogError(self::LOGLVL_ERROR, $ErrMsg);
5114 $DstFile = NULL;
5115 }
5116 }
5117 else
5118 {
5119 # log error and set destination file path to indicate failure
5120 $this->LogError(self::LOGLVL_ERROR,
5121 "Unable to write out minimized JavaScript to file ".$DstFile);
5122 $DstFile = NULL;
5123 }
5124 }
5125
5126 # return CSS file path to caller
5127 return $DstFile;
5128 }
5129
5137 private function CssUrlFingerprintInsertion($Matches)
5138 {
5139 # generate fingerprint string from CSS file modification time
5140 $FileName = realpath($this->CssUrlFingerprintPath."/".
5141 $Matches[2].".".$Matches[3]);
5142 $MTime = filemtime($FileName);
5143 $Fingerprint = sprintf("%06X", ($MTime % 0xFFFFFF));
5144
5145 # build URL string with fingerprint and return it to caller
5146 return "url(".$Matches[1].$Matches[2].".".$Fingerprint
5147 .".".$Matches[3].$Matches[4].")";
5148 }
5149
5157 private function GetRequiredFilesNotYetLoaded($PageContentFile)
5158 {
5159 # start out assuming no files required
5160 $RequiredFiles = array();
5161
5162 # if page content file supplied
5163 if ($PageContentFile)
5164 {
5165 # if file containing list of required files is available
5166 $Path = dirname($PageContentFile);
5167 $RequireListFile = $Path."/REQUIRES";
5168 if (file_exists($RequireListFile))
5169 {
5170 # read in list of required files
5171 $RequestedFiles = file($RequireListFile);
5172
5173 # for each line in required file list
5174 foreach ($RequestedFiles as $Line)
5175 {
5176 # if line is not a comment
5177 $Line = trim($Line);
5178 if (!preg_match("/^#/", $Line))
5179 {
5180 # if file has not already been loaded
5181 if (!in_array($Line, $this->FoundUIFiles))
5182 {
5183 # add to list of required files
5184 $RequiredFiles[$Line] = self::ORDER_MIDDLE;
5185 }
5186 }
5187 }
5188 }
5189 }
5190
5191 # add in additional required files if any
5192 if (count($this->AdditionalRequiredUIFiles))
5193 {
5194 # make sure there are no duplicates
5195 $AdditionalRequiredUIFiles = array_unique(
5196 $this->AdditionalRequiredUIFiles);
5197
5198 $RequiredFiles = array_merge(
5199 $RequiredFiles, $this->AdditionalRequiredUIFiles);
5200 }
5201
5202 # return list of required files to caller
5203 return $RequiredFiles;
5204 }
5205
5214 private function SubBrowserIntoFileNames($FileNames)
5215 {
5216 # if a browser detection function has been made available
5217 $UpdatedFileNames = array();
5218 if (is_callable($this->BrowserDetectFunc))
5219 {
5220 # call function to get browser list
5221 $Browsers = call_user_func($this->BrowserDetectFunc);
5222
5223 # for each required file
5224 foreach ($FileNames as $FileName => $Value)
5225 {
5226 # if file name includes browser keyword
5227 if (preg_match("/%BROWSER%/", $FileName))
5228 {
5229 # for each browser
5230 foreach ($Browsers as $Browser)
5231 {
5232 # substitute in browser name and add to new file list
5233 $NewFileName = preg_replace(
5234 "/%BROWSER%/", $Browser, $FileName);
5235 $UpdatedFileNames[$NewFileName] = $Value;
5236 }
5237 }
5238 else
5239 {
5240 # add to new file list
5241 $UpdatedFileNames[$FileName] = $Value;
5242 }
5243 }
5244 }
5245 else
5246 {
5247 # filter out any files with browser keyword in their name
5248 foreach ($FileNames as $FileName => $Value)
5249 {
5250 if (!preg_match("/%BROWSER%/", $FileName))
5251 {
5252 $UpdatedFileNames[$FileName] = $Value;
5253 }
5254 }
5255 }
5256
5257 return $UpdatedFileNames;
5258 }
5259
5265 private function AddMetaTagsToPageOutput($PageOutput)
5266 {
5267 # start with unconditional (non-unique) tags
5268 $TagsToAdd = $this->MetaTags;
5269
5270 # for each unique tag
5271 foreach ($this->UniqueMetaTags as $UniqueMetaTag)
5272 {
5273 $Attribs = $UniqueMetaTag["Attribs"];
5274 $UniqueAttribs = $UniqueMetaTag["UniqueAttribs"];
5275
5276 # if no unique attributes specified
5277 if ($UniqueAttribs === NULL)
5278 {
5279 # use first attribute as unique attribute
5280 $UniqueAttribs = array_slice($Attribs, 0, 1);
5281 }
5282
5283 # for each already-queued tag
5284 # (look for meta tags that match all attributes in
5285 # the current unique tag)
5286 foreach ($TagsToAdd as $TagAttribs)
5287 {
5288 # for each attribute in current unique tag
5289 # (look for attributes in the current unique tag that do
5290 # not match attributes in the this queued tag)
5291 foreach ($UniqueAttribs as $UniqueName => $UniqueValue)
5292 {
5293 # if unique attribute is not found in queued tag
5294 # or queued tag attribute has a different value
5295 if (!isset($TagAttribs[$UniqueName])
5296 || ($TagAttribs[$UniqueName] != $UniqueValue))
5297 {
5298 # skip to next queued tag
5299 # (some attribute in the current unique tag
5300 # was not found in the queued tag)
5301 continue 2;
5302 }
5303 }
5304
5305 # skip to next unique tag
5306 # (all attributes in the current unique tag were found
5307 # in the queued tag, so do not queue this unique tag)
5308 continue 2;
5309 }
5310
5311 # generate potential combinations of unique attributes
5312 $UniqueAttribNameCombos = StdLib::ArrayPermutations(
5313 array_keys($UniqueAttribs));
5314
5315 # for each combination of unique attributes
5316 foreach ($UniqueAttribNameCombos as $UniqueNameCombo)
5317 {
5318 # for each attribute in combination
5319 $AttribStrings = array();
5320 foreach ($UniqueNameCombo as $UniqueName)
5321 {
5322 # add attrib/value string to list
5323 $AttribStrings[] = $UniqueName."=\""
5324 .htmlspecialchars($UniqueAttribs[$UniqueName])."\"";
5325 }
5326
5327 # build search string from list of attribute pairs
5328 $SearchString = "<meta ".implode(" ", $AttribStrings);
5329
5330 # if search string appears in page output
5331 if (strpos($PageOutput, $SearchString) !== FALSE)
5332 {
5333 # skip to next unique tag
5334 continue 2;
5335 }
5336
5337 # repeat search with single quotes instead of double quotes
5338 $SearchString = strtr($SearchString, '"', "'");
5339 if (strpos($PageOutput, $SearchString) !== FALSE)
5340 {
5341 # skip to next unique tag
5342 continue 2;
5343 }
5344 }
5345
5346 # unique tag was not found in page output, so add it to inserted tags
5347 $TagsToAdd[] = $Attribs;
5348 }
5349
5350 # if there are meta tags to be added
5351 if (count($TagsToAdd))
5352 {
5353 # start with an empty segment
5354 $Section = "";
5355
5356 # for each meta tag
5357 foreach ($TagsToAdd as $Attribs)
5358 {
5359 # assemble tag and add it to the segment
5360 $Section .= "<meta";
5361 foreach ($Attribs as $AttribName => $AttribValue)
5362 {
5363 $Section .= " ".$AttribName."=\""
5364 .htmlspecialchars(trim($AttribValue))."\"";
5365 }
5366 $Section .= " />\n";
5367 }
5368
5369 # if standard page start and end have been disabled
5370 if ($this->SuppressStdPageStartAndEnd)
5371 {
5372 # add segment to beginning of page output
5373 $PageOutput = $Section.$PageOutput;
5374 }
5375 else
5376 {
5377 # insert segment at beginning of HTML head section in page output
5378 $PageOutput = preg_replace("#<head>#i",
5379 "<head>\n".$Section, $PageOutput, 1);
5380 }
5381 }
5382
5383 # return (potentially modified) page output to caller
5384 return $PageOutput;
5385 }
5386
5394 private function AddFileTagsToPageOutput($PageOutput, $Files)
5395 {
5396 # substitute browser name into names of required files as appropriate
5397 $Files = $this->SubBrowserIntoFileNames($Files);
5398
5399 # initialize content sections
5400 $HeadContent = [
5401 self::ORDER_FIRST => "",
5402 self::ORDER_MIDDLE => "",
5403 self::ORDER_LAST => "",
5404 ];
5405 $BodyContent = [
5406 self::ORDER_FIRST => "",
5407 self::ORDER_MIDDLE => "",
5408 self::ORDER_LAST => "",
5409 ];
5410
5411 # for each required file
5412 foreach ($Files as $File => $Order)
5413 {
5414 # locate specific file to use
5415 $FilePath = $this->GUIFile($File);
5416
5417 # if file was found
5418 if ($FilePath)
5419 {
5420 # generate tag for file
5421 $Tag = $this->GetUIFileLoadingTag($FilePath);
5422
5423 # add file to HTML output based on file type
5424 $FileType = $this->GetFileType($FilePath);
5425 switch ($FileType)
5426 {
5427 case self::FT_CSS:
5428 $HeadContent[$Order] .= $Tag."\n";
5429 break;
5430
5431 case self::FT_JAVASCRIPT:
5432 $BodyContent[$Order] .= $Tag."\n";
5433 break;
5434 }
5435 }
5436 }
5437
5438 # add content to head
5439 $Replacement = $HeadContent[self::ORDER_MIDDLE]
5440 .$HeadContent[self::ORDER_LAST];
5441 $UpdatedPageOutput = str_ireplace("</head>",
5442 $Replacement."</head>",
5443 $PageOutput, $ReplacementCount);
5444 # (if no </head> tag was found, just prepend tags to page content)
5445 if ($ReplacementCount == 0)
5446 {
5447 $PageOutput = $Replacement.$PageOutput;
5448 }
5449 # (else if multiple </head> tags found, only prepend tags to the first)
5450 elseif ($ReplacementCount > 1)
5451 {
5452 $PageOutput = preg_replace("#</head>#i",
5453 $Replacement."</head>",
5454 $PageOutput, 1);
5455 }
5456 else
5457 {
5458 $PageOutput = $UpdatedPageOutput;
5459 }
5460 $Replacement = $HeadContent[self::ORDER_FIRST];
5461 $UpdatedPageOutput = str_ireplace("<head>",
5462 "<head>\n".$Replacement,
5463 $PageOutput, $ReplacementCount);
5464 # (if no <head> tag was found, just prepend tags to page content)
5465 if ($ReplacementCount == 0)
5466 {
5467 $PageOutput = $Replacement.$PageOutput;
5468 }
5469 # (else if multiple <head> tags found, only append tags to the first)
5470 elseif ($ReplacementCount > 1)
5471 {
5472 $PageOutput = preg_replace("#<head>#i",
5473 "<head>\n".$Replacement,
5474 $PageOutput, 1);
5475 }
5476 else
5477 {
5478 $PageOutput = $UpdatedPageOutput;
5479 }
5480
5481 # add content to body
5482 $Replacement = $BodyContent[self::ORDER_FIRST];
5483 $PageOutput = preg_replace("#<body([^>]*)>#i",
5484 "<body\\1>\n".$Replacement,
5485 $PageOutput, 1, $ReplacementCount);
5486 # (if no <body> tag was found, just append tags to page content)
5487 if ($ReplacementCount == 0)
5488 {
5489 $PageOutput = $PageOutput.$Replacement;
5490 }
5491 $Replacement = $BodyContent[self::ORDER_MIDDLE]
5492 .$BodyContent[self::ORDER_LAST];
5493 $UpdatedPageOutput = str_ireplace("</body>",
5494 $Replacement."\n</body>",
5495 $PageOutput, $ReplacementCount);
5496 # (if no </body> tag was found, just append tags to page content)
5497 if ($ReplacementCount == 0)
5498 {
5499 $PageOutput = $PageOutput.$Replacement;
5500 }
5501 # (else if multiple </body> tags found, only prepend tag to the first)
5502 elseif ($ReplacementCount > 1)
5503 {
5504 $PageOutput = preg_replace("#</body>#i",
5505 $Replacement."\n</body>",
5506 $PageOutput, 1);
5507 }
5508 else
5509 {
5510 $PageOutput = $UpdatedPageOutput;
5511 }
5512
5513 return $PageOutput;
5514 }
5515
5526 private function GetUIFileLoadingTag($FileName, $AdditionalAttributes = NULL)
5527 {
5528 # pad additional attributes if supplied
5529 $AddAttribs = $AdditionalAttributes ? " ".$AdditionalAttributes : "";
5530
5531 # retrieve type of UI file
5532 $FileType = $this->GetFileType($FileName);
5533
5534 # construct tag based on file type
5535 switch ($FileType)
5536 {
5537 case self::FT_CSS:
5538 $Tag = " <link rel=\"stylesheet\" type=\"text/css\""
5539 ." media=\"all\" href=\"".$FileName."\""
5540 .$AddAttribs." />\n";
5541 break;
5542
5543 case self::FT_JAVASCRIPT:
5544 $Tag = " <script type=\"text/javascript\""
5545 ." src=\"".$FileName."\""
5546 .$AddAttribs."></script>\n";
5547 break;
5548
5549 case self::FT_IMAGE:
5550 $Tag = "<img src=\"".$FileName."\"".$AddAttribs.">";
5551 break;
5552
5553 default:
5554 $Tag = "";
5555 break;
5556 }
5557
5558 # return constructed tag to caller
5559 return $Tag;
5560 }
5561
5566 private function AutoloadObjects($ClassName)
5567 {
5568 # if caching is not turned off
5569 # and we have a cached location for class
5570 # and file at cached location is readable
5571 if ((self::$ObjectLocationCacheInterval > 0)
5572 && array_key_exists($ClassName,
5573 self::$ObjectLocationCache)
5574 && is_readable(self::$ObjectLocationCache[$ClassName]))
5575 {
5576 # use object location from cache
5577 require_once(self::$ObjectLocationCache[$ClassName]);
5578 }
5579 else
5580 {
5581 # for each possible object file directory
5582 static $FileLists;
5583 foreach (self::$ObjectDirectories as $Location => $Info)
5584 {
5585 # make any needed replacements in directory path
5586 $Location = str_replace(array("%ACTIVEUI%", "%DEFAULTUI%"),
5587 array(self::$ActiveUI, self::$DefaultUI), $Location);
5588
5589 # if directory looks valid
5590 if (is_dir($Location))
5591 {
5592 # pass class name through callback (if supplied)
5593 $ClassFileName = $ClassName;
5594 if (is_callable($Info["Callback"]))
5595 {
5596 $ClassFileName = $Info["Callback"]($ClassFileName);
5597 }
5598
5599 # strip off any namespace prefix
5600 foreach ($Info["NamespacePrefixes"] as $Prefix)
5601 {
5602 if (strpos($ClassFileName, $Prefix) === 0)
5603 {
5604 $ClassFileName = substr($ClassFileName, strlen($Prefix));
5605 break;
5606 }
5607 }
5608
5609 # strip off any leading namespace separator
5610 if (strpos($ClassFileName, "\\") === 0)
5611 {
5612 $ClassFileName = substr($ClassFileName, 1);
5613 }
5614
5615 # convert any namespace separators to directory separators
5616 $ClassFileName = str_replace("\\", "/", $ClassFileName);
5617
5618 # finish building class file name
5619 $ClassFileName = $ClassFileName.".php";
5620
5621 # read in directory contents if not already retrieved
5622 if (!isset($FileLists[$Location]))
5623 {
5624 $FileLists[$Location] = self::ReadDirectoryTree(
5625 $Location, '/^.+\.php$/i');
5626 }
5627
5628 # for each file in target directory
5629 foreach ($FileLists[$Location] as $FileName)
5630 {
5631 # if file matches our target object file name
5632 if ($FileName == $ClassFileName)
5633 {
5634 # include object file
5635 require_once($Location.$FileName);
5636
5637 # save location to cache
5638 self::$ObjectLocationCache[$ClassName]
5639 = $Location.$FileName;
5640
5641 # set flag indicating that cache should be saved
5642 self::$SaveObjectLocationCache = TRUE;
5643
5644 # stop looking
5645 break 2;
5646 }
5647 }
5648 }
5649 }
5650 }
5651 }
5652
5660 private static function ReadDirectoryTree($Directory, $Pattern)
5661 {
5662 $CurrentDir = getcwd();
5663 chdir($Directory);
5664 $DirIter = new RecursiveDirectoryIterator(".");
5665 $IterIter = new RecursiveIteratorIterator($DirIter);
5666 $RegexResults = new RegexIterator($IterIter, $Pattern,
5667 RecursiveRegexIterator::GET_MATCH);
5668 $FileList = array();
5669 foreach ($RegexResults as $Result)
5670 {
5671 $FileList[] = substr($Result[0], 2);
5672 }
5673 chdir($CurrentDir);
5674 return $FileList;
5675 }
5676
5681 private function LoadUIFunctions()
5682 {
5683 $Dirs = array(
5684 "local/interface/%ACTIVEUI%/include",
5685 "interface/%ACTIVEUI%/include",
5686 "local/interface/%DEFAULTUI%/include",
5687 "interface/%DEFAULTUI%/include",
5688 );
5689 foreach ($Dirs as $Dir)
5690 {
5691 $Dir = str_replace(array("%ACTIVEUI%", "%DEFAULTUI%"),
5692 array(self::$ActiveUI, self::$DefaultUI), $Dir);
5693 if (is_dir($Dir))
5694 {
5695 $FileNames = scandir($Dir);
5696 foreach ($FileNames as $FileName)
5697 {
5698 if (preg_match("/^F-([A-Za-z0-9_]+)\.php/",
5699 $FileName, $Matches)
5700 || preg_match("/^F-([A-Za-z0-9_]+)\.html/",
5701 $FileName, $Matches))
5702 {
5703 if (!function_exists($Matches[1]))
5704 {
5705 include_once($Dir."/".$FileName);
5706 }
5707 }
5708 }
5709 }
5710 }
5711 }
5712
5718 private function ProcessPeriodicEvent($EventName, $Callback)
5719 {
5720 # retrieve last execution time for event if available
5721 $Signature = self::GetCallbackSignature($Callback);
5722 $LastRun = $this->DB->Query("SELECT LastRunAt FROM PeriodicEvents"
5723 ." WHERE Signature = '".addslashes($Signature)."'", "LastRunAt");
5724
5725 # determine whether enough time has passed for event to execute
5726 $ShouldExecute = (($LastRun === NULL)
5727 || (time() > (strtotime($LastRun) + $this->EventPeriods[$EventName])))
5728 ? TRUE : FALSE;
5729
5730 # if event should run
5731 if ($ShouldExecute)
5732 {
5733 # add event to task queue
5734 $WrapperCallback = array("ApplicationFramework", "RunPeriodicEvent");
5735 $WrapperParameters = array(
5736 $EventName, $Callback, array("LastRunAt" => $LastRun));
5737 $this->QueueUniqueTask($WrapperCallback, $WrapperParameters);
5738 }
5739
5740 # add event to list of periodic events
5741 $this->KnownPeriodicEvents[$Signature] = array(
5742 "Period" => $EventName,
5743 "Callback" => $Callback,
5744 "Queued" => $ShouldExecute);
5745 }
5746
5752 private static function GetCallbackSignature($Callback)
5753 {
5754 return !is_array($Callback) ? $Callback
5755 : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
5756 ."::".$Callback[1];
5757 }
5758
5763 private function PrepForTSR()
5764 {
5765 # if HTML has been output and it's time to launch another task
5766 # (only TSR if HTML has been output because otherwise browsers
5767 # may misbehave after connection is closed)
5768 if ((PHP_SAPI != "cli")
5769 && ($this->JumpToPage || !$this->SuppressHTML)
5770 && !$this->LoadedViaAlternateDomain()
5771 && (time() > (strtotime($this->Settings["LastTaskRunAt"])
5772 + ($this->MaxExecutionTime()
5773 / $this->Settings["MaxTasksRunning"]) + 5))
5774 && $this->GetTaskQueueSize()
5775 && $this->Settings["TaskExecutionEnabled"])
5776 {
5777 # begin buffering output for TSR
5778 ob_start();
5779
5780 # let caller know it is time to launch another task
5781 return TRUE;
5782 }
5783 else
5784 {
5785 # let caller know it is not time to launch another task
5786 return FALSE;
5787 }
5788 }
5789
5794 private function LaunchTSR()
5795 {
5796 # set headers to close out connection to browser
5797 if (!$this->NoTSR)
5798 {
5799 ignore_user_abort(TRUE);
5800 header("Connection: close");
5801 header("Content-Length: ".ob_get_length());
5802 }
5803
5804 # output buffered content
5805 while (ob_get_level()) { ob_end_flush(); }
5806 flush();
5807
5808 # write out any outstanding data and end HTTP session
5809 session_write_close();
5810
5811 # set flag indicating that we are now running in background
5812 $this->RunningInBackground = TRUE;
5813
5814 # handle garbage collection for session data
5815 if (isset($this->SessionStorage) &&
5816 (rand()/getrandmax()) <= $this->SessionGcProbability)
5817 {
5818 # determine when sessions will expire
5819 $ExpiredTime = strtotime("-". self::$SessionLifetime." seconds");
5820
5821 # iterate over files in the session directory with a DirectoryIterator
5822 # NB: we cannot use scandir() here because it reads the
5823 # entire list of files into memory and may exceed the memory
5824 # limit for directories with very many files
5825 $DI = new DirectoryIterator($this->SessionStorage);
5826 while ($DI->valid())
5827 {
5828 if ((strpos($DI->getFilename(), "sess_") === 0) &&
5829 $DI->isFile() &&
5830 $DI->getCTime() < $ExpiredTime)
5831 {
5832 unlink($DI->getPathname());
5833 }
5834 $DI->next();
5835 }
5836 unset($DI);
5837 }
5838
5839 # if there is still a task in the queue
5840 if ($this->GetTaskQueueSize())
5841 {
5842 # tell PHP to garbage collect to give as much memory as possible for tasks
5843 gc_collect_cycles();
5844
5845 # turn on output buffering to (hopefully) record any crash output
5846 ob_start();
5847
5848 # lock tables and grab last task run time to double check
5849 $this->DB->Query("LOCK TABLES ApplicationFrameworkSettings WRITE");
5850 $this->LoadSettings();
5851
5852 # if still time to launch another task
5853 if (time() > (strtotime($this->Settings["LastTaskRunAt"])
5854 + ($this->MaxExecutionTime()
5855 / $this->Settings["MaxTasksRunning"]) + 5))
5856 {
5857 # update the "last run" time and release tables
5858 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
5859 ." SET LastTaskRunAt = '".date("Y-m-d H:i:s")."'");
5860 $this->DB->Query("UNLOCK TABLES");
5861
5862 # run tasks while there is a task in the queue
5863 # and enough time and memory left
5864 do
5865 {
5866 # run the next task
5867 $this->RunNextTask();
5868
5869 # calculate percentage of memory still available
5870 $PercentFreeMem = (self::GetFreeMemory()
5871 / self::GetPhpMemoryLimit()) * 100;
5872 }
5873 while ($this->GetTaskQueueSize()
5874 && ($this->GetSecondsBeforeTimeout() > 65)
5875 && ($PercentFreeMem > $this->BackgroundTaskMinFreeMemPercent));
5876
5877 $this->ResetTaskIdGeneratorIfNecessary();
5878 }
5879 else
5880 {
5881 # release tables
5882 $this->DB->Query("UNLOCK TABLES");
5883 }
5884 }
5885 }
5886
5896 private function GetTaskList($DBQuery, $Count, $Offset)
5897 {
5898 $this->DB->Query($DBQuery." LIMIT ".intval($Offset).",".intval($Count));
5899 $Tasks = array();
5900 while ($Row = $this->DB->FetchRow())
5901 {
5902 $Tasks[$Row["TaskId"]] = $Row;
5903 if ($Row["Callback"] ==
5904 serialize(array("ApplicationFramework", "RunPeriodicEvent")))
5905 {
5906 $WrappedCallback = unserialize($Row["Parameters"]);
5907 $Tasks[$Row["TaskId"]]["Callback"] = $WrappedCallback[1];
5908 $Tasks[$Row["TaskId"]]["Parameters"] = NULL;
5909 }
5910 else
5911 {
5912 $Tasks[$Row["TaskId"]]["Callback"] = unserialize($Row["Callback"]);
5913 $Tasks[$Row["TaskId"]]["Parameters"] = unserialize($Row["Parameters"]);
5914 }
5915 }
5916 return $Tasks;
5917 }
5918
5922 private function RunNextTask()
5923 {
5924 # lock tables to prevent same task from being run by multiple sessions
5925 $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
5926
5927 # look for task at head of queue
5928 $this->DB->Query("SELECT * FROM TaskQueue ORDER BY Priority, TaskId LIMIT 1");
5929 $Task = $this->DB->FetchRow();
5930
5931 # if there was a task available
5932 if ($Task)
5933 {
5934 # move task from queue to running tasks list
5935 $this->DB->Query("INSERT INTO RunningTasks "
5936 ."(TaskId,Callback,Parameters,Priority,Description) "
5937 ."SELECT * FROM TaskQueue WHERE TaskId = "
5938 .intval($Task["TaskId"]));
5939 $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = "
5940 .intval($Task["TaskId"]));
5941
5942 # release table locks to again allow other sessions to run tasks
5943 $this->DB->Query("UNLOCK TABLES");
5944
5945 # unpack stored task info
5946 $TaskId = $Task["TaskId"];
5947 $Callback = unserialize($Task["Callback"]);
5948 $Parameters = unserialize($Task["Parameters"]);
5949
5950 # attempt to load task callback if not already available
5951 $this->LoadFunction($Callback);
5952
5953 # clear task requeue flag
5954 $this->RequeueCurrentTask = FALSE;
5955
5956 # save amount of free memory for later comparison
5957 $BeforeFreeMem = self::GetFreeMemory();
5958
5959 # run task
5960 $this->RunningTask = $Task;
5961 if ($Parameters)
5962 {
5963 call_user_func_array($Callback, $Parameters);
5964 }
5965 else
5966 {
5967 call_user_func($Callback);
5968 }
5969 unset($this->RunningTask);
5970
5971 # log if task leaked significant memory
5972 $this->LogTaskMemoryLeakIfAny($TaskId, $BeforeFreeMem);
5973
5974 # if task requeue requested
5975 if ($this->RequeueCurrentTask)
5976 {
5977 # move task from running tasks list to queue
5978 $this->RequeueRunningTask($TaskId);
5979 }
5980 else
5981 {
5982 # remove task from running tasks list
5983 $this->DB->Query("DELETE FROM RunningTasks"
5984 ." WHERE TaskId = ".intval($TaskId));
5985 }
5986
5987 # prune running tasks list if necessary
5988 $RunningTasksCount = $this->DB->Query(
5989 "SELECT COUNT(*) AS TaskCount FROM RunningTasks", "TaskCount");
5990 if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
5991 {
5992 $this->DB->Query("DELETE FROM RunningTasks ORDER BY StartedAt"
5993 ." LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
5994 }
5995 }
5996 else
5997 {
5998 # release table locks to again allow other sessions to run tasks
5999 $this->DB->Query("UNLOCK TABLES");
6000 }
6001 }
6002
6008 private function RequeueRunningTask($TaskId)
6009 {
6010 $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
6011 $this->DB->Query("INSERT INTO TaskQueue"
6012 ." (Callback,Parameters,Priority,Description)"
6013 ." SELECT Callback,Parameters,Priority,Description"
6014 ." FROM RunningTasks WHERE TaskId = ".intval($TaskId));
6015 $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
6016 $this->DB->Query("UNLOCK TABLES");
6017 }
6018
6024 private function ResetTaskIdGeneratorIfNecessary()
6025 {
6026 $this->DB->Query("LOCK TABLES TaskQueue WRITE");
6027 if ($this->GetTaskQueueSize() == 0 && ($this->GetSecondsBeforeTimeout() > 30) &&
6028 $this->DB->GetNextInsertId("TaskQueue") > 0.90 * Database::INT_MAX_VALUE)
6029 {
6030 $this->DB->Query("TRUNCATE TABLE TaskQueue");
6031 }
6032 $this->DB->Query("UNLOCK TABLES");
6033 }
6034
6041 private function LogTaskMemoryLeakIfAny($TaskId, $StartingFreeMemory)
6042 {
6043 # tell PHP to garbage collect to free up any memory no longer used
6044 gc_collect_cycles();
6045
6046 # calculate the logging threshold
6047 $LeakThreshold = self::GetPhpMemoryLimit()
6048 * ($this->BackgroundTaskMemLeakLogThreshold / 100);
6049
6050 # calculate the amount of memory used by task
6051 $EndingFreeMemory = self::GetFreeMemory();
6052 $MemoryUsed = $StartingFreeMemory - $EndingFreeMemory;
6053
6054 # if amount of memory used is over threshold
6055 if ($MemoryUsed > $LeakThreshold)
6056 {
6057 # log memory leak
6058 $TaskSynopsis = self::GetTaskCallbackSynopsis($this->GetTask($TaskId));
6059 $this->LogError(self::LOGLVL_DEBUG, "Task ".$TaskSynopsis." leaked "
6060 .number_format($MemoryUsed)." bytes.");
6061 }
6062 }
6063
6069 public function OnCrash()
6070 {
6071 # attempt to remove any memory limits
6072 $FreeMemory = $this->GetFreeMemory();
6073 ini_set("memory_limit", -1);
6074
6075 # if there is a background task currently running
6076 if (isset($this->RunningTask))
6077 {
6078 # add info about current page load
6079 $CrashInfo["ElapsedTime"] = $this->GetElapsedExecutionTime();
6080 $CrashInfo["FreeMemory"] = $FreeMemory;
6081 $CrashInfo["REMOTE_ADDR"] = $_SERVER["REMOTE_ADDR"];
6082 $CrashInfo["REQUEST_URI"] = $_SERVER["REQUEST_URI"];
6083 if (isset($_SERVER["REQUEST_TIME"]))
6084 {
6085 $CrashInfo["REQUEST_TIME"] = $_SERVER["REQUEST_TIME"];
6086 }
6087 if (isset($_SERVER["REMOTE_HOST"]))
6088 {
6089 $CrashInfo["REMOTE_HOST"] = $_SERVER["REMOTE_HOST"];
6090 }
6091
6092 # add info about error that caused crash (if available)
6093 if (function_exists("error_get_last"))
6094 {
6095 $CrashInfo["LastError"] = error_get_last();
6096 }
6097
6098 # add info about current output buffer contents (if available)
6099 if (ob_get_length() !== FALSE)
6100 {
6101 $CrashInfo["OutputBuffer"] = ob_get_contents();
6102 }
6103
6104 # if backtrace info is available for the crash
6105 $Backtrace = debug_backtrace();
6106 if (count($Backtrace) > 1)
6107 {
6108 # discard the current context from the backtrace
6109 array_shift($Backtrace);
6110
6111 # add the backtrace to the crash info
6112 $CrashInfo["Backtrace"] = $Backtrace;
6113 }
6114 # else if saved backtrace info is available
6115 elseif (isset($this->SavedContext))
6116 {
6117 # add the saved backtrace to the crash info
6118 $CrashInfo["Backtrace"] = $this->SavedContext;
6119 }
6120
6121 # save crash info for currently running task
6122 $DB = new Database();
6123 $DB->Query("UPDATE RunningTasks SET CrashInfo = '"
6124 .addslashes(serialize($CrashInfo))
6125 ."' WHERE TaskId = ".intval($this->RunningTask["TaskId"]));
6126 }
6127
6128 print("\n");
6129 return;
6130 }
6131
6148 private function AddToDirList($DirList, $Dir, $SearchLast, $SkipSlashCheck)
6149 {
6150 # convert incoming directory to array of directories (if needed)
6151 $Dirs = is_array($Dir) ? $Dir : array($Dir);
6152
6153 # reverse array so directories are searched in specified order
6154 $Dirs = array_reverse($Dirs);
6155
6156 # for each directory
6157 foreach ($Dirs as $Location)
6158 {
6159 # make sure directory includes trailing slash
6160 if (!$SkipSlashCheck)
6161 {
6162 $Location = $Location
6163 .((substr($Location, -1) != "/") ? "/" : "");
6164 }
6165
6166 # remove directory from list if already present
6167 if (in_array($Location, $DirList))
6168 {
6169 $DirList = array_diff(
6170 $DirList, array($Location));
6171 }
6172
6173 # add directory to list of directories
6174 if ($SearchLast)
6175 {
6176 array_push($DirList, $Location);
6177 }
6178 else
6179 {
6180 array_unshift($DirList, $Location);
6181 }
6182 }
6183
6184 # return updated directory list to caller
6185 return $DirList;
6186 }
6187
6194 private function OutputModificationCallbackShell($Matches)
6195 {
6196 # call previously-stored external function
6197 return call_user_func($this->OutputModificationCallbackInfo["Callback"],
6198 $Matches,
6199 $this->OutputModificationCallbackInfo["Pattern"],
6200 $this->OutputModificationCallbackInfo["Page"],
6201 $this->OutputModificationCallbackInfo["SearchPattern"]);
6202 }
6203
6212 private function CheckOutputModification($Original, $Modified, $ErrorInfo)
6213 {
6214 # if error was reported by regex engine
6215 if (preg_last_error() !== PREG_NO_ERROR)
6216 {
6217 # log error
6218 $this->LogError(self::LOGLVL_ERROR,
6219 "Error reported by regex engine when modifying output."
6220 ." (".$ErrorInfo.")");
6221
6222 # use unmodified version of output
6223 $OutputToUse = $Original;
6224 }
6225 # else if modification reduced output by more than threshold
6226 elseif ((strlen(trim($Modified)) / strlen(trim($Original)))
6227 < self::OUTPUT_MODIFICATION_THRESHOLD)
6228 {
6229 # log error
6230 $this->LogError(self::LOGLVL_WARNING,
6231 "Content reduced below acceptable threshold while modifying output."
6232 ." (".$ErrorInfo.")");
6233
6234 # use unmodified version of output
6235 $OutputToUse = $Original;
6236 }
6237 else
6238 {
6239 # use modified version of output
6240 $OutputToUse = $Modified;
6241 }
6242
6243 # return output to use to caller
6244 return $OutputToUse;
6245 }
6246
6248 const OUTPUT_MODIFICATION_THRESHOLD = 0.10;
6249
6259 private function UpdateSetting(
6260 $FieldName, $NewValue = DB_NOVALUE, $Persistent = TRUE)
6261 {
6262 static $LocalSettings;
6263 if ($NewValue !== DB_NOVALUE)
6264 {
6265 if ($Persistent)
6266 {
6267 $LocalSettings[$FieldName] = $this->DB->UpdateValue(
6268 "ApplicationFrameworkSettings",
6269 $FieldName, $NewValue, NULL, $this->Settings);
6270 }
6271 else
6272 {
6273 $LocalSettings[$FieldName] = $NewValue;
6274 }
6275 }
6276 elseif (!isset($LocalSettings[$FieldName]))
6277 {
6278 $LocalSettings[$FieldName] = $this->DB->UpdateValue(
6279 "ApplicationFrameworkSettings",
6280 $FieldName, $NewValue, NULL, $this->Settings);
6281 }
6282 return $LocalSettings[$FieldName];
6283 }
6284
6294 private static function IncludeFile($_AF_File, $_AF_ContextVars = array())
6295 {
6296 # set up context
6297 foreach ($_AF_ContextVars as $_AF_VarName => $_AF_VarValue)
6298 {
6299 $$_AF_VarName = $_AF_VarValue;
6300 }
6301 unset($_AF_VarName);
6302 unset($_AF_VarValue);
6303 unset($_AF_ContextVars);
6304
6305 # add variables to context that we assume are always available
6306 $AF = $GLOBALS["AF"];
6307
6308 # load file
6309 include($_AF_File);
6310
6311 # return updated context
6312 $ContextVars = get_defined_vars();
6313 unset($ContextVars["_AF_File"]);
6314 return $ContextVars;
6315 }
6316
6323 private function FilterContext($Context, $ContextVars)
6324 {
6325 # clear all variables if no setting for context is available
6326 # or setting is FALSE
6327 if (!isset($this->ContextFilters[$Context])
6328 || ($this->ContextFilters[$Context] == FALSE))
6329 {
6330 return array();
6331 }
6332 # keep all variables if setting for context is TRUE
6333 elseif ($this->ContextFilters[$Context] == TRUE)
6334 {
6335 return $ContextVars;
6336 }
6337 else
6338 {
6339 $Prefixes = $this->ContextFilters[$Context];
6340 $FilterFunc = function($VarName) use ($Prefixes) {
6341 foreach ($Prefixes as $Prefix)
6342 {
6343 if (substr($VarName, $Prefix) === 0)
6344 {
6345 return TRUE;
6346 }
6347 }
6348 return FALSE;
6349 };
6350 return array_filter(
6351 $ContextVars, $FilterFunc, ARRAY_FILTER_USE_KEY);
6352 }
6353 }
6354
6356 private $InterfaceDirList = array(
6357 "interface/%ACTIVEUI%/",
6358 "interface/%DEFAULTUI%/",
6359 );
6364 private $IncludeDirList = array(
6365 "interface/%ACTIVEUI%/include/",
6366 "interface/%ACTIVEUI%/objects/",
6367 "interface/%DEFAULTUI%/include/",
6368 "interface/%DEFAULTUI%/objects/",
6369 );
6371 private $ImageDirList = array(
6372 "interface/%ACTIVEUI%/images/",
6373 "interface/%DEFAULTUI%/images/",
6374 );
6376 private $FunctionDirList = array(
6377 "interface/%ACTIVEUI%/include/",
6378 "interface/%DEFAULTUI%/include/",
6379 "include/",
6380 );
6381
6382 private $ExpandedDirectoryLists = array();
6383
6384 const NOVALUE = ".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
6385
6386
6387 # ---- Page Caching (Internal Methods) -----------------------------------
6388
6394 private function CheckForCachedPage($PageName)
6395 {
6396 # assume no cached page will be found
6397 $CachedPage = NULL;
6398
6399 # if returning a cached page is allowed
6400 if ($this->CacheCurrentPage)
6401 {
6402 # get fingerprint for requested page
6403 $PageFingerprint = $this->GetPageFingerprint($PageName);
6404
6405 # look for matching page in cache in database
6406 $this->DB->Query("SELECT * FROM AF_CachedPages"
6407 ." WHERE Fingerprint = '".addslashes($PageFingerprint)."'");
6408
6409 # if matching page found
6410 if ($this->DB->NumRowsSelected())
6411 {
6412 # if cached page has expired
6413 $Row = $this->DB->FetchRow();
6414 $ExpirationTime = strtotime(
6415 "-".$this->PageCacheExpirationPeriod()." seconds");
6416 if (strtotime($Row["CachedAt"]) < $ExpirationTime)
6417 {
6418 # clear expired pages from cache
6419 $ExpirationTimestamp = date("Y-m-d H:i:s", $ExpirationTime);
6420 $this->DB->Query("DELETE CP, CPTI FROM AF_CachedPages CP,"
6421 ." AF_CachedPageTagInts CPTI"
6422 ." WHERE CP.CachedAt < '".$ExpirationTimestamp."'"
6423 ." AND CPTI.CacheId = CP.CacheId");
6424 $this->DB->Query("DELETE FROM AF_CachedPages "
6425 ." WHERE CachedAt < '".$ExpirationTimestamp."'");
6426 }
6427 else
6428 {
6429 # display cached page and exit
6430 $CachedPage = $Row["PageContent"];
6431 }
6432 }
6433 }
6434
6435 # return any cached page found to caller
6436 return $CachedPage;
6437 }
6438
6444 private function UpdatePageCache($PageName, $PageContent)
6445 {
6446 # if page caching is enabled and current page should be cached
6447 if ($this->PageCacheEnabled()
6448 && $this->CacheCurrentPage
6449 && ($PageName != "404"))
6450 {
6451 # if page content looks invalid
6452 if (strlen(trim(strip_tags($PageContent))) == 0)
6453 {
6454 # log error
6455 $LogMsg = "Page not cached because content was empty."
6456 ." (PAGE: ".$PageName.", URL: ".$this->FullUrl().")";
6457 $this->LogError(self::LOGLVL_ERROR, $LogMsg);
6458 }
6459 else
6460 {
6461 # save page to cache
6462 $PageFingerprint = $this->GetPageFingerprint($PageName);
6463 $this->DB->Query("INSERT INTO AF_CachedPages"
6464 ." (Fingerprint, PageContent) VALUES"
6465 ." ('".$this->DB->EscapeString($PageFingerprint)."', '"
6466 .$this->DB->EscapeString($PageContent)."')");
6467 $CacheId = $this->DB->LastInsertId();
6468
6469 # for each page cache tag that was added
6470 foreach ($this->PageCacheTags as $Tag => $Pages)
6471 {
6472 # if current page is in list for tag
6473 if (in_array("CURRENT", $Pages) || in_array($PageName, $Pages))
6474 {
6475 # look up tag ID
6476 $TagId = $this->GetPageCacheTagId($Tag);
6477
6478 # mark current page as associated with tag
6479 $this->DB->Query("INSERT INTO AF_CachedPageTagInts"
6480 ." (TagId, CacheId) VALUES "
6481 ." (".intval($TagId).", ".intval($CacheId).")");
6482 }
6483 }
6484 }
6485 }
6486 }
6487
6493 private function GetPageCacheTagId($Tag)
6494 {
6495 # if tag is a non-negative integer
6496 if (is_numeric($Tag) && ($Tag > 0) && (intval($Tag) == $Tag))
6497 {
6498 # generate ID
6499 $Id = self::PAGECACHETAGIDOFFSET + $Tag;
6500 }
6501 else
6502 {
6503 # look up ID in database
6504 $Id = $this->DB->Query("SELECT TagId FROM AF_CachedPageTags"
6505 ." WHERE Tag = '".addslashes($Tag)."'", "TagId");
6506
6507 # if ID was not found
6508 if ($Id === NULL)
6509 {
6510 # add tag to database
6511 $this->DB->Query("INSERT INTO AF_CachedPageTags"
6512 ." SET Tag = '".addslashes($Tag)."'");
6513 $Id = $this->DB->LastInsertId();
6514 }
6515 }
6516
6517 # return tag ID to caller
6518 return $Id;
6519 }
6520
6526 private function GetPageFingerprint($PageName)
6527 {
6528 # only get the environmental fingerprint once so that it is consistent
6529 # between page construction start and end
6530 static $EnvFingerprint;
6531 if (!isset($EnvFingerprint))
6532 {
6533 $EnvData = json_encode($_GET).json_encode($_POST)
6534 .$_SERVER["SERVER_NAME"];
6535 $EnvFingerprint = md5($EnvData);
6536 }
6537
6538 # build page fingerprint and return it to caller
6539 return $PageName."-".$EnvFingerprint;
6540 }
6541
6548 private function UpdateLastUsedTimeForActiveSessions()
6549 {
6550 if ($this->SessionInUse)
6551 {
6552 $_SESSION["AF_SessionLastUsed"] = date("Y-m-d H:i:s");
6553 }
6554 elseif (isset($_SESSION["AF_SessionLastUsed"]))
6555 {
6556 unset($_SESSION["AF_SessionLastUsed"]);
6557 }
6558 }
6559 }
ApplicationFramework\LOGLVL_ERROR
const LOGLVL_ERROR
ERROR error logging level.
Definition: ApplicationFramework.php:2399
ApplicationFramework\PageCacheEnabled
PageCacheEnabled($NewValue=DB_NOVALUE, $Persistent=FALSE)
Enable/disable page caching.
Definition: ApplicationFramework.php:1944
ApplicationFramework\UnhookEvent
UnhookEvent($EventsOrEventName, $Callback=NULL, $Order=self::ORDER_MIDDLE)
Unhook one or more functions that were previously hooked to be called when the specified event is sig...
Definition: ApplicationFramework.php:2581
ApplicationFramework\GetOrphanedTaskList
GetOrphanedTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently orphaned.
Definition: ApplicationFramework.php:3027
ApplicationFramework\SuppressHTMLOutput
SuppressHTMLOutput($NewSetting=TRUE)
Suppress loading of HTML files.
Definition: ApplicationFramework.php:1241
ApplicationFramework\AddInterfaceDirectories
AddInterfaceDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for user interface (HTML/TPL) files.
Definition: ApplicationFramework.php:398
ApplicationFramework\AddIncludeDirectories
AddIncludeDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for user interface include (CSS, JavaScript, common PHP, common HTML, etc) files.
Definition: ApplicationFramework.php:371
ApplicationFramework\ObjectLocationCacheExpirationInterval
ObjectLocationCacheExpirationInterval($NewInterval=DB_NOVALUE, $Persistent=FALSE)
Get/set object file location cache expiration period in minutes.
Definition: ApplicationFramework.php:495
ApplicationFramework\GetScriptUrl
static GetScriptUrl()
Retrieve SCRIPT_URL server value, pulling it from elsewhere if that variable isn&#39;t set...
Definition: ApplicationFramework.php:3832
ApplicationFramework\LOGLVL_INFO
const LOGLVL_INFO
INFO error logging level.
Definition: ApplicationFramework.php:2388
ApplicationFramework\AddUnbufferedCallback
AddUnbufferedCallback($Callback, $Parameters=array())
Add a callback that will be executed after buffered content has been output and that won&#39;t have its o...
Definition: ApplicationFramework.php:453
ApplicationFramework\DoNotUrlFingerprint
DoNotUrlFingerprint($Pattern)
Specify file or file name pattern to exclude from URL fingerprinting.
Definition: ApplicationFramework.php:1755
ApplicationFramework\QueueUniqueTask
QueueUniqueTask($Callback, $Parameters=NULL, $Priority=self::PRIORITY_LOW, $Description="")
Add task to queue if not already in queue or currently running.
Definition: ApplicationFramework.php:2887
ApplicationFramework\AddPrefixForAlternateDomain
AddPrefixForAlternateDomain($Domain, $Prefix)
Add an alternate domain for the site which should map to a path tree under the main site URL...
Definition: ApplicationFramework.php:3611
ApplicationFramework\ConvertPhpIniSizeToBytes
static ConvertPhpIniSizeToBytes($Size)
Convert an abbreviated size from php.ini (e.g., 2g) to a number of bytes.
Definition: ApplicationFramework.php:4253
ApplicationFramework\RequeueCurrentTask
RequeueCurrentTask($NewValue=TRUE)
Set whether to requeue the currently-running background task when it completes.
Definition: ApplicationFramework.php:3076
ApplicationFramework\LOGLVL_FATAL
const LOGLVL_FATAL
FATAL error logging level.
Definition: ApplicationFramework.php:2404
ApplicationFramework\AddMetaTag
AddMetaTag($Attribs)
Add meta tag to page output.
Definition: ApplicationFramework.php:1905
ApplicationFramework\AddPostProcessingCall
AddPostProcessingCall($FunctionName, &$Arg1=self::NOVALUE, &$Arg2=self::NOVALUE, &$Arg3=self::NOVALUE, &$Arg4=self::NOVALUE, &$Arg5=self::NOVALUE, &$Arg6=self::NOVALUE, &$Arg7=self::NOVALUE, &$Arg8=self::NOVALUE, &$Arg9=self::NOVALUE)
Add function to be called after HTML has been loaded.
Definition: ApplicationFramework.php:1443
ApplicationFramework\GetCleanUrlForPath
GetCleanUrlForPath($Path)
Get the clean URL mapped for a path.
Definition: ApplicationFramework.php:3477
ApplicationFramework\PRIORITY_LOW
const PRIORITY_LOW
Lower priority.
Definition: ApplicationFramework.php:2842
ApplicationFramework\IsRunningInBackground
IsRunningInBackground()
Determine whether currently running inside a background task.
Definition: ApplicationFramework.php:3264
Message
Abstraction for forum messages and resource comments.
Definition: Message.php:14
ApplicationFramework\GetQueuedTaskList
GetQueuedTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently in queue.
Definition: ApplicationFramework.php:2959
ApplicationFramework\LogFile
LogFile($NewValue=NULL)
Get/set log file name.
Definition: ApplicationFramework.php:2276
ApplicationFramework\GetCleanUrl
GetCleanUrl()
Get the clean URL for the current page if one is available.
Definition: ApplicationFramework.php:3572
ApplicationFramework\CONTEXT_END
const CONTEXT_END
File loading context: page end file.
Definition: ApplicationFramework.php:1515
ApplicationFramework\PAGECACHETAGIDOFFSET
const PAGECACHETAGIDOFFSET
Definition: ApplicationFramework.php:4403
ApplicationFramework\PreferHttpHost
static PreferHttpHost($NewValue=NULL)
Get/set whether to prefer $_SERVER["HTTP_HOST"] (if available) over $_SERVER["SERVER_NAME"] when dete...
Definition: ApplicationFramework.php:3802
ApplicationFramework\ClearObjectLocationCache
ClearObjectLocationCache()
Clear object (class) file location cache.
Definition: ApplicationFramework.php:505
ApplicationFramework\GetLock
GetLock($LockName, $Wait=TRUE)
Get an exclusive ("write") lock on the specified name.
Definition: ApplicationFramework.php:4102
ApplicationFramework\FullUrl
static FullUrl()
Get current full URL, before any clean URL remapping and with any query string (e.g.
Definition: ApplicationFramework.php:3787
ApplicationFramework
Top-level framework for web applications.
Definition: ApplicationFramework.php:14
ApplicationFramework\BaseUrl
static BaseUrl()
Get current base URL (the part before index.php) (e.g.
Definition: ApplicationFramework.php:3773
ApplicationFramework\GetOrphanedTaskCount
GetOrphanedTaskCount()
Retrieve current number of orphaned tasks.
Definition: ApplicationFramework.php:3039
Database
SQL database abstraction object with smart query caching.
Definition: Database.php:22
ApplicationFramework\ActiveUserInterface
static ActiveUserInterface($UIName=NULL)
Get/set name of current active user interface.
Definition: ApplicationFramework.php:1278
ApplicationFramework\SessionInUse
SessionInUse($InUse=NULL)
Get/Set value of SessionInUse, which indicates if the current session is currently in use...
Definition: ApplicationFramework.php:4293
ApplicationFramework\GetTaskQueueSize
GetTaskQueueSize($Priority=NULL)
Retrieve current number of tasks in queue.
Definition: ApplicationFramework.php:2947
StdLib\SortCompare
static SortCompare($A, $B)
Perform compare and return value appropriate for sort function callbacks.
Definition: StdLib.php:572
ApplicationFramework\RootUrlOverride
static RootUrlOverride($NewValue=self::NOVALUE)
Get/set root URL override.
Definition: ApplicationFramework.php:3755
ApplicationFramework\GetNextLowerBackgroundPriority
GetNextLowerBackgroundPriority($Priority=NULL)
Get next lower possible background task priority.
Definition: ApplicationFramework.php:3310
ApplicationFramework\UrlFingerprintingRewriteSupport
static UrlFingerprintingRewriteSupport()
Determine if rewrite support for URL fingerprinting is available.
Definition: ApplicationFramework.php:3675
ApplicationFramework\GetQueuedTaskCount
GetQueuedTaskCount($Callback=NULL, $Parameters=NULL, $Priority=NULL, $Description=NULL)
Get number of queued tasks that match supplied values.
Definition: ApplicationFramework.php:2978
ApplicationFramework\DeleteTask
DeleteTask($TaskId)
Remove task from task queues.
Definition: ApplicationFramework.php:3086
ApplicationFramework\LOGLVL_DEBUG
const LOGLVL_DEBUG
DEBUG error logging level.
Definition: ApplicationFramework.php:2382
ApplicationFramework\ReachedViaAjax
static ReachedViaAjax($NewSetting=NULL)
Determine if we were reached via an AJAX-based (or other automated) page load.
Definition: ApplicationFramework.php:3894
ApplicationFramework\EVENTTYPE_NAMED
const EVENTTYPE_NAMED
Named result event type.
Definition: ApplicationFramework.php:2441
ApplicationFramework\EVENTTYPE_FIRST
const EVENTTYPE_FIRST
First response event type.
Definition: ApplicationFramework.php:2433
ApplicationFramework\WasUrlRewritten
static WasUrlRewritten($ScriptName="index.php")
Determine if the URL was rewritten, i.e., the script is being accessed through a URL that isn&#39;t direc...
Definition: ApplicationFramework.php:3861
ApplicationFramework\EVENTTYPE_DEFAULT
const EVENTTYPE_DEFAULT
Default event type.
Definition: ApplicationFramework.php:2421
ApplicationFramework\GetPageUrl
GetPageUrl()
Get the full URL to the page.
Definition: ApplicationFramework.php:1142
JShrink\Minifier\minify
static minify($js, $options=array())
Takes a string containing javascript and removes unneeded characters in order to shrink the code with...
Definition: Minifier.php:103
ApplicationFramework\CONTEXT_ENV
const CONTEXT_ENV
File loading context: environmental include files.
Definition: ApplicationFramework.php:1505
ApplicationFramework\TemplateLocationCacheExpirationInterval
TemplateLocationCacheExpirationInterval($NewInterval=DB_NOVALUE, $Persistent=FALSE)
Get/set UI template location cache expiration period in minutes.
Definition: ApplicationFramework.php:470
ApplicationFramework\RequireUIFile
RequireUIFile($FileNames, $Order=self::ORDER_MIDDLE)
Add file to list of required UI files.
Definition: ApplicationFramework.php:1771
ApplicationFramework\SetBrowserCacheExpirationTime
SetBrowserCacheExpirationTime($MaxAge)
Set headers to control client-side caching of data served to the browser in this page load (usually J...
Definition: ApplicationFramework.php:4239
ApplicationFramework\IsStaticOnlyEvent
IsStaticOnlyEvent($EventName)
Report whether specified event only allows static callbacks.
Definition: ApplicationFramework.php:2707
ApplicationFramework\CONTEXT_PAGE
const CONTEXT_PAGE
File loading context: PHP page file (from "pages").
Definition: ApplicationFramework.php:1507
ApplicationFramework\PageCacheExpirationPeriod
PageCacheExpirationPeriod($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set page cache expiration period in seconds.
Definition: ApplicationFramework.php:1959
ApplicationFramework\IsRegisteredEvent
IsRegisteredEvent($EventName)
Check if event has been registered (is available to be signaled).
Definition: ApplicationFramework.php:2479
ApplicationFramework\ReleaseLock
ReleaseLock($LockName)
Release lock with specified name.
Definition: ApplicationFramework.php:4175
ApplicationFramework\SignalEvent
SignalEvent($EventName, $Parameters=NULL)
Signal that an event has occured.
Definition: ApplicationFramework.php:2624
ApplicationFramework\BasePath
static BasePath()
Get current base path (usually the part after the host name).
Definition: ApplicationFramework.php:3815
ApplicationFramework\LOGLVL_TRACE
const LOGLVL_TRACE
TRACE error logging level.
Definition: ApplicationFramework.php:2377
ApplicationFramework\LOGLVL_WARNING
const LOGLVL_WARNING
WARNING error logging level.
Definition: ApplicationFramework.php:2393
Database\INT_MAX_VALUE
const INT_MAX_VALUE
Definition: Database.php:1288
ApplicationFramework\PRIORITY_MEDIUM
const PRIORITY_MEDIUM
Medium (default) priority.
Definition: ApplicationFramework.php:2840
ApplicationFramework\LogError
LogError($Level, $Msg)
Write error message to log.
Definition: ApplicationFramework.php:2143
ApplicationFramework\LogHighMemoryUsage
LogHighMemoryUsage($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether logging of high memory usage is enabled.
Definition: ApplicationFramework.php:2107
ApplicationFramework\ScssRewriteSupport
static ScssRewriteSupport()
Determine if SCSS rewrite support is available.
Definition: ApplicationFramework.php:3687
ApplicationFramework\OnCrash
OnCrash()
Called automatically at program termination to ensure output is written out.
Definition: ApplicationFramework.php:6069
ApplicationFramework\GetKnownPeriodicEvents
GetKnownPeriodicEvents()
Get list of known periodic events.
Definition: ApplicationFramework.php:2757
ApplicationFramework\JavascriptMinimizationEnabled
JavascriptMinimizationEnabled($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether the application framework will attempt to generate minimized JavaScript.
Definition: ApplicationFramework.php:583
ApplicationFramework\UseMinimizedJavascript
UseMinimizedJavascript($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether minimized JavaScript will be searched for and used if found.
Definition: ApplicationFramework.php:567
ApplicationFramework\EVENTTYPE_CHAIN
const EVENTTYPE_CHAIN
Result chaining event type.
Definition: ApplicationFramework.php:2427
ApplicationFramework\SlowPageLoadThreshold
SlowPageLoadThreshold($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set how long a page load can take before it should be considered "slow" and may be logged...
Definition: ApplicationFramework.php:2088
ApplicationFramework\AddMetaTagOnce
AddMetaTagOnce($Attribs, $UniqueAttribs=NULL)
Add meta tag to page output if not already present.
Definition: ApplicationFramework.php:1919
ApplicationFramework\ClearPageCacheForTag
ClearPageCacheForTag($Tag)
Clear all cached pages associated with specified tag.
Definition: ApplicationFramework.php:2011
ApplicationFramework\DoNotCacheCurrentPage
DoNotCacheCurrentPage()
Prevent the current page from being cached.
Definition: ApplicationFramework.php:1969
ApplicationFramework\SuppressStandardPageStartAndEnd
SuppressStandardPageStartAndEnd($NewSetting=TRUE)
Suppress loading of standard page start and end files.
Definition: ApplicationFramework.php:1253
scssc
SCSS compiler written in PHP.
Definition: scssc.php:45
ApplicationFramework\GetSecondsBeforeTimeout
GetSecondsBeforeTimeout()
Get remaining available (PHP) execution time.
Definition: ApplicationFramework.php:1896
ApplicationFramework\TaskIsInQueue
TaskIsInQueue($Callback, $Parameters=NULL)
Check if task is already in queue or currently running.
Definition: ApplicationFramework.php:2924
ApplicationFramework\GetCurrentBackgroundPriority
GetCurrentBackgroundPriority()
Determine current priority if running in background.
Definition: ApplicationFramework.php:3274
ApplicationFramework\AddFunctionDirectories
AddFunctionDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for function ("F-") files.
Definition: ApplicationFramework.php:429
ApplicationFramework\GetAlternateDomains
GetAlternateDomains()
Get the list of configured alternate domains.
Definition: ApplicationFramework.php:3621
StdLib\ArrayPermutations
static ArrayPermutations($Items, $Perms=array())
Return all possible permutations of a given array.
Definition: StdLib.php:721
ApplicationFramework\AddPageCacheTag
AddPageCacheTag($Tag, $Pages=NULL)
Add caching tag for current page or specified pages.
Definition: ApplicationFramework.php:1980
ApplicationFramework\GetPageCacheInfo
GetPageCacheInfo()
Get page cache information.
Definition: ApplicationFramework.php:2040
ApplicationFramework\ORDER_MIDDLE
const ORDER_MIDDLE
Handle item after ORDER_FIRST and before ORDER_LAST items.
Definition: ApplicationFramework.php:2446
ApplicationFramework\OUTPUT_MODIFICATION_THRESHOLD
const OUTPUT_MODIFICATION_THRESHOLD
Threshold below which page output modifications are considered to have failed.
Definition: ApplicationFramework.php:6248
ApplicationFramework\RegisterEvent
RegisterEvent($EventsOrEventName, $EventType=NULL)
Register one or more events that may be signaled.
Definition: ApplicationFramework.php:2458
ApplicationFramework\UrlFingerprintingEnabled
UrlFingerprintingEnabled($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether URL fingerprinting is enabled.
Definition: ApplicationFramework.php:520
PHP
PHP
Definition: RSSClient--Test.php:12
ApplicationFramework\JsMinRewriteSupport
static JsMinRewriteSupport()
Determine if rewrite support for JavaScript minification is available.
Definition: ApplicationFramework.php:3699
ApplicationFramework\DownloadFile
DownloadFile($FilePath, $FileName=NULL, $MimeType=NULL)
Send specified file for download by user.
Definition: ApplicationFramework.php:3995
ApplicationFramework\GetPageName
GetPageName()
Get name of page being loaded.
Definition: ApplicationFramework.php:1099
ApplicationFramework\LogSlowPageLoads
LogSlowPageLoads($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether logging of long page load times is enabled.
Definition: ApplicationFramework.php:2072
DB_NOVALUE
const DB_NOVALUE
Definition: Database.php:1738
ApplicationFramework\CleanUrlIsMapped
CleanUrlIsMapped($Path)
Report whether clean URL has already been mapped.
Definition: ApplicationFramework.php:3456
ApplicationFramework\HighMemoryUsageThreshold
HighMemoryUsageThreshold($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set what percentage of max memory (set via the memory_limit PHP configuration directive) a page l...
Definition: ApplicationFramework.php:2124
ApplicationFramework\SetJumpToPage
SetJumpToPage($Page, $Delay=0, $IsLiteral=FALSE)
Set URL of page to autoload after PHP page file is executed.
Definition: ApplicationFramework.php:1158
ApplicationFramework\MIN_GC_PROBABILITY
const MIN_GC_PROBABILITY
Definition: ApplicationFramework.php:4406
ApplicationFramework\CONTEXT_START
const CONTEXT_START
File loading context: page start file.
Definition: ApplicationFramework.php:1513
ApplicationFramework\CONTEXT_COMMON
const CONTEXT_COMMON
File loading context: common HTML files.
Definition: ApplicationFramework.php:1509
ApplicationFramework\PRIORITY_HIGH
const PRIORITY_HIGH
Highest priority.
Definition: ApplicationFramework.php:2838
ApplicationFramework\LoadFunction
LoadFunction($Callback)
Attempt to load code for function or method if not currently available.
Definition: ApplicationFramework.php:1837
ApplicationFramework\GetNextHigherBackgroundPriority
GetNextHigherBackgroundPriority($Priority=NULL)
Get next higher possible background task priority.
Definition: ApplicationFramework.php:3288
ApplicationFramework\GetRunningTaskList
GetRunningTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently running.
Definition: ApplicationFramework.php:3012
ApplicationFramework\FindCommonTemplate
FindCommonTemplate($BaseName)
Preserved for backward compatibility for use with code written prior to October 2012.
Definition: ApplicationFramework.php:4316
ApplicationFramework\EventWillNextRunAt
EventWillNextRunAt($EventName, $Callback)
Get date/time a periodic event will next run.
Definition: ApplicationFramework.php:2722
ApplicationFramework\SessionLifetime
static SessionLifetime($NewValue=NULL)
Get/set session timeout in seconds.
Definition: ApplicationFramework.php:3649
ApplicationFramework\DefaultUserInterface
static DefaultUserInterface($UIName=NULL)
Get/set name of current default user interface.
Definition: ApplicationFramework.php:1263
ApplicationFramework\HookEvent
HookEvent($EventsOrEventName, $Callback=NULL, $Order=self::ORDER_MIDDLE)
Hook one or more functions to be called when the specified event is signaled.
Definition: ApplicationFramework.php:2516
ApplicationFramework\ClearPageCache
ClearPageCache()
Clear all pages from page cache.
Definition: ApplicationFramework.php:2026
ApplicationFramework\IsHookedEvent
IsHookedEvent($EventName)
Check if an event is registered and is hooked to.
Definition: ApplicationFramework.php:2491
ApplicationFramework\GetFreeMemory
static GetFreeMemory()
Get current amount of free memory.
Definition: ApplicationFramework.php:3922
ApplicationFramework\FT_JAVASCRIPT
const FT_JAVASCRIPT
JavaScript file type.
Definition: ApplicationFramework.php:1827
ApplicationFramework\CONTEXT_INTERFACE
const CONTEXT_INTERFACE
File loading context: HTML interface file.
Definition: ApplicationFramework.php:1511
ApplicationFramework\AddImageDirectories
AddImageDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for image files.
Definition: ApplicationFramework.php:343
ApplicationFramework\FT_CSS
const FT_CSS
CSS file type.
Definition: ApplicationFramework.php:1823
ApplicationFramework\HtmlCharset
HtmlCharset($NewSetting=NULL)
Get/set HTTP character encoding value.
Definition: ApplicationFramework.php:1197
ApplicationFramework\ORDER_FIRST
const ORDER_FIRST
Handle item first (i.e.
Definition: ApplicationFramework.php:2444
StdLib\SQL_DATE_FORMAT
const SQL_DATE_FORMAT
Format to feed to date() to get SQL-compatible date/time string.
Definition: StdLib.php:880
ApplicationFramework\GetPhpMaxUploadSize
static GetPhpMaxUploadSize()
Get the maximum size for a file upload in bytes.
Definition: ApplicationFramework.php:3944
ApplicationFramework\IncludeUIFile
IncludeUIFile($FileNames, $AdditionalAttributes=NULL)
Search UI directories for specified JavaScript or CSS file and print HTML tag to load file...
Definition: ApplicationFramework.php:1701
ApplicationFramework\GetUncleanUrlForPath
GetUncleanUrlForPath($Path)
Get the unclean URL for mapped for a path.
Definition: ApplicationFramework.php:3522
ApplicationFramework\FT_IMAGE
const FT_IMAGE
Image (GIF/JPG/PNG) file type.
Definition: ApplicationFramework.php:1825
ApplicationFramework\ClearTemplateLocationCache
ClearTemplateLocationCache()
Clear template location cache.
Definition: ApplicationFramework.php:480
ApplicationFramework\GetPrefixForAlternateDomain
GetPrefixForAlternateDomain($Domain)
Get configured prefix for an alternate domain, if one exists.
Definition: ApplicationFramework.php:3632
ApplicationFramework\GetFileType
static GetFileType($FileName)
Determine type of specified file based on the file name.
Definition: ApplicationFramework.php:1788
ApplicationFramework\GetUncleanUrl
GetUncleanUrl()
Get the unclean URL for the current page.
Definition: ApplicationFramework.php:3581
ApplicationFramework\DoNotMinimizeFile
DoNotMinimizeFile($File)
Specify file(s) to not attempt to minimize.
Definition: ApplicationFramework.php:1212
ApplicationFramework\RecordContextInCaseOfCrash
RecordContextInCaseOfCrash($BacktraceOptions=0, $BacktraceLimit=0)
Record the current execution context in case of crash.
Definition: ApplicationFramework.php:602
ApplicationFramework\JumpToPageIsSet
JumpToPageIsSet()
Report whether a page to autoload has been set.
Definition: ApplicationFramework.php:1183
ApplicationFramework\ORDER_LAST
const ORDER_LAST
Handle item last (i.e.
Definition: ApplicationFramework.php:2448
ApplicationFramework\LoggingLevel
LoggingLevel($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set logging level.
Definition: ApplicationFramework.php:2258
ApplicationFramework\UseBaseTag
UseBaseTag($NewValue=NULL)
Get/set whether or not to use the "base" tag to ensure relative URL paths are correct.
Definition: ApplicationFramework.php:1228
ApplicationFramework\HtaccessSupport
static HtaccessSupport()
Determine if .htaccess files are enabled.
Definition: ApplicationFramework.php:3663
ApplicationFramework\GetTask
GetTask($TaskId)
Retrieve task info from queue (either running or queued tasks).
Definition: ApplicationFramework.php:3102
ApplicationFramework\MaxExecutionTime
MaxExecutionTime($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set maximum PHP execution time.
Definition: ApplicationFramework.php:3965
ApplicationFramework\GetPhpMemoryLimit
static GetPhpMemoryLimit()
Get PHP memory limit in bytes.
Definition: ApplicationFramework.php:3932
ApplicationFramework\SetContextFilter
SetContextFilter($Context, $NewSetting)
Configure filtering of variables left in the execution environment for the next loaded file after a P...
Definition: ApplicationFramework.php:1486
ApplicationFramework\LoadPage
LoadPage($PageName)
Load page PHP and HTML/TPL files.
Definition: ApplicationFramework.php:621
ApplicationFramework\GetUserInterfacePaths
GetUserInterfacePaths($FilterExp=NULL)
Get list of available user interfaces and the relative paths to the base directory for each interface...
Definition: ApplicationFramework.php:1348
ApplicationFramework\AddEnvInclude
AddEnvInclude($FileName)
Add file to be included to set up environment.
Definition: ApplicationFramework.php:1465
ApplicationFramework\GetCleanUrlList
GetCleanUrlList()
Get list of all clean URLs currently added.
Definition: ApplicationFramework.php:3594
ApplicationFramework\NOVALUE
const NOVALUE
Definition: ApplicationFramework.php:6384
ApplicationFramework\GetUserInterfaces
GetUserInterfaces($FilterExp=NULL)
Get list of available user interfaces and their labels.
Definition: ApplicationFramework.php:1297
ApplicationFramework\ReQueueOrphanedTask
ReQueueOrphanedTask($TaskId, $NewPriority=NULL)
Move orphaned task back into queue.
Definition: ApplicationFramework.php:3052
ApplicationFramework\GetLogEntries
GetLogEntries($Limit=0)
Get log entries, in reverse chronological order.
Definition: ApplicationFramework.php:2291
ApplicationFramework\GUIFile
GUIFile($FileName)
Search UI directories for specified image or CSS file and return name of correct file.
Definition: ApplicationFramework.php:1523
ApplicationFramework\QueueTask
QueueTask($Callback, $Parameters=NULL, $Priority=self::PRIORITY_LOW, $Description="")
Add task to queue.
Definition: ApplicationFramework.php:2858
ApplicationFramework\TaskExecutionEnabled
TaskExecutionEnabled($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether automatic task execution is enabled.
Definition: ApplicationFramework.php:3153
ApplicationFramework\GetTaskCallbackSynopsis
static GetTaskCallbackSynopsis($TaskInfo)
Get printable synopsis for task callback.
Definition: ApplicationFramework.php:3178
ApplicationFramework\PUIFile
PUIFile($FileName)
Search UI directories for specified interface (image, CSS, JavaScript etc) file and print name of cor...
Definition: ApplicationFramework.php:1681
ApplicationFramework\GenerateCompactCss
GenerateCompactCss($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether generating compact CSS (when compiling SCSS) is enabled.
Definition: ApplicationFramework.php:551
ApplicationFramework\LOGFILE_MAX_LINE_LENGTH
const LOGFILE_MAX_LINE_LENGTH
Maximum length for a line in the log file.
Definition: ApplicationFramework.php:2409
ApplicationFramework\GetPageLocation
GetPageLocation()
Get the URL path to the page without the base path, if present.
Definition: ApplicationFramework.php:1109
ApplicationFramework\ScssSupportEnabled
ScssSupportEnabled($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether SCSS compilation support is enabled.
Definition: ApplicationFramework.php:535
ApplicationFramework\BeginAjaxResponse
BeginAjaxResponse($ResponseType="JSON", $CloseSession=TRUE)
Begin an AJAX response, setting the necessary HTTP headers and optionally closing the PHP session...
Definition: ApplicationFramework.php:4203
ApplicationFramework\MaxTasks
MaxTasks($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set maximum number of tasks to have running simultaneously.
Definition: ApplicationFramework.php:3166
ApplicationFramework\GetElapsedExecutionTime
GetElapsedExecutionTime()
Get time elapsed since constructor was called.
Definition: ApplicationFramework.php:1887
ApplicationFramework\RunPeriodicEvent
static RunPeriodicEvent($EventName, $Callback, $Parameters)
Run periodic event and then save info needed to know when to run it again.
Definition: ApplicationFramework.php:2799
ApplicationFramework\SetBrowserDetectionFunc
SetBrowserDetectionFunc($DetectionFunc)
Specify function to use to detect the web browser type.
Definition: ApplicationFramework.php:442
ApplicationFramework\RootUrl
static RootUrl()
Get portion of current URL through host name, with no trailing slash (e.g.
Definition: ApplicationFramework.php:3712
ApplicationFramework\AddCleanUrl
AddCleanUrl($Pattern, $Page, $GetVars=NULL, $Template=NULL)
Add clean URL mapping.
Definition: ApplicationFramework.php:3357
JavaScriptPacker
Definition: JavaScriptPacker.php:69
StdLib\GetCallerInfo
static GetCallerInfo($Element=NULL)
Get info about call to current function.
Definition: StdLib.php:26
ApplicationFramework\PRIORITY_BACKGROUND
const PRIORITY_BACKGROUND
Lowest priority.
Definition: ApplicationFramework.php:2844
ApplicationFramework\LogMessage
LogMessage($Level, $Msg)
Write status message to log.
Definition: ApplicationFramework.php:2185
ApplicationFramework\FT_OTHER
const FT_OTHER
File type other than CSS, image, or JavaScript.
Definition: ApplicationFramework.php:1821
ApplicationFramework\AddObjectDirectory
static AddObjectDirectory($Dir, $NamespacePrefixes=array(), $Callback=NULL)
Add directory to be searched for object files when autoloading.
Definition: ApplicationFramework.php:284