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 # save execution start time
28 $this->ExecutionStartTime = microtime(TRUE);
29
30 # set up default object file search locations
31 self::AddObjectDirectory("local/interface/%ACTIVEUI%/objects");
32 self::AddObjectDirectory("interface/%ACTIVEUI%/objects");
33 self::AddObjectDirectory("local/interface/%DEFAULTUI%/objects");
34 self::AddObjectDirectory("interface/%DEFAULTUI%/objects");
35 self::AddObjectDirectory("local/objects");
36 self::AddObjectDirectory("objects");
37
38 # set up object file autoloader
39 spl_autoload_register(array($this, "AutoloadObjects"));
40
41 # set up function to output any buffered text in case of crash
42 register_shutdown_function(array($this, "OnCrash"));
43
44 # if we were not invoked via command line interface
45 if (php_sapi_name() !== "cli")
46 {
47 # build cookie domain string
48 $SessionDomain = isset($_SERVER["SERVER_NAME"]) ? $_SERVER["SERVER_NAME"]
49 : isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"]
50 : php_uname("n");
51
52 # if it appears our session storage area is writable
53 if (is_writable(session_save_path()))
54 {
55 # store our session files in a subdirectory to avoid
56 # accidentally sharing sessions with other installations
57 # on the same domain
58 $SessionStorage = session_save_path()
59 ."/".self::$AppName."_".md5($SessionDomain.dirname(__FILE__));
60
61 # create session storage subdirectory if not found
62 if (!is_dir($SessionStorage)) { mkdir($SessionStorage, 0700 ); }
63
64 # if session storage subdirectory is writable
65 if (is_writable($SessionStorage))
66 {
67 # save parameters of our session storage as instance variables
68 # for later use
69 $this->SessionGcProbability =
70 ini_get("session.gc_probability") / ini_get("session.gc_divisor");
71 # require a gc probability of at least MIN_GC_PROBABILITY
72 if ($this->SessionGcProbability < self::MIN_GC_PROBABILITY)
73 {
74 $this->SessionGcProbability = self::MIN_GC_PROBABILITY;
75 }
76
77 $this->SessionStorage = $SessionStorage;
78
79 # set the new session storage location
80 session_save_path($SessionStorage);
81
82 # disable PHP's garbage collection, as it does not handle
83 # subdirectories (instead, we'll do the cleanup as we run
84 # background tasks)
85 ini_set("session.gc_probability", 0);
86 }
87 }
88
89 # set garbage collection max period to our session lifetime
90 ini_set("session.gc_maxlifetime", self::$SessionLifetime);
91 session_set_cookie_params(
92 self::$SessionLifetime, "/", $SessionDomain);
93
94 # attempt to start session
95 $SessionStarted = @session_start();
96
97 # if session start failed
98 if (!$SessionStarted)
99 {
100 # regenerate session ID and attempt to start session again
101 session_regenerate_id(TRUE);
102 session_start();
103 }
104 }
105
106 # set up our internal environment
107 $this->DB = new Database();
108
109 # set up our exception handler
110 set_exception_handler(array($this, "GlobalExceptionHandler"));
111
112 # perform any work needed to undo PHP magic quotes
113 $this->UndoMagicQuotes();
114
115 # load our settings from database
116 $this->LoadSettings();
117
118 # set PHP maximum execution time
119 ini_set("max_execution_time", $this->Settings["MaxExecTime"]);
120 set_time_limit($this->Settings["MaxExecTime"]);
121
122 # register events we handle internally
123 $this->RegisterEvent($this->PeriodicEvents);
124 $this->RegisterEvent($this->UIEvents);
125
126 # attempt to create SCSS cache directory if needed and it does not exist
127 if ($this->ScssSupportEnabled() && !is_dir(self::$ScssCacheDir))
128 { @mkdir(self::$ScssCacheDir, 0777, TRUE); }
129
130 # attempt to create minimized JS cache directory if needed and it does not exist
131 if ($this->UseMinimizedJavascript()
132 && $this->JavascriptMinimizationEnabled()
133 && !is_dir(self::$JSMinCacheDir))
134 {
135 @mkdir(self::$JSMinCacheDir, 0777, TRUE);
136 }
137 }
144 public function __destruct()
145 {
146 # if template location cache is flagged to be saved
147 if ($this->SaveTemplateLocationCache)
148 {
149 # write template location cache out and update cache expiration
150 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
151 ." SET TemplateLocationCache = '"
152 .addslashes(serialize(
153 $this->TemplateLocationCache))."',"
154 ." TemplateLocationCacheExpiration = '"
155 .date("Y-m-d H:i:s",
156 $this->TemplateLocationCacheExpiration)."'");
157 }
158
159 # if object location cache is flagged to be saved
160 if (self::$SaveObjectLocationCache)
161 {
162 # write object location cache out and update cache expiration
163 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
164 ." SET ObjectLocationCache = '"
165 .addslashes(serialize(
166 self::$ObjectLocationCache))."',"
167 ." ObjectLocationCacheExpiration = '"
168 .date("Y-m-d H:i:s",
169 self::$ObjectLocationCacheExpiration)."'");
170 }
171 }
179 public function GlobalExceptionHandler($Exception)
180 {
181 # display exception info
182 $Location = str_replace(getcwd()."/", "",
183 $Exception->getFile()."[".$Exception->getLine()."]");
184 ?><table width="100%" cellpadding="5"
185 style="border: 2px solid #666666; background: #CCCCCC;
186 font-family: Courier New, Courier, monospace;
187 margin-top: 10px;"><tr><td>
188 <div style="color: #666666;">
189 <span style="font-size: 150%;">
190 <b>Uncaught Exception</b></span><br />
191 <b>Message:</b> <i><?PHP print $Exception->getMessage(); ?></i><br />
192 <b>Location:</b> <i><?PHP print $Location; ?></i><br />
193 <b>Trace:</b>
194 <blockquote><pre><?PHP print preg_replace(
195 ":(#[0-9]+) ".getcwd()."/".":", "$1 ",
196 $Exception->getTraceAsString());
197 ?></pre></blockquote>
198 </div>
199 </td></tr></table><?PHP
200
201 # log exception if possible
202 $TraceString = $Exception->getTraceAsString();
203 $TraceString = str_replace("\n", ", ", $TraceString);
204 $TraceString = preg_replace(":(#[0-9]+) ".getcwd()."/".":", "$1 ", $TraceString);
205 $LogMsg = "Uncaught exception (".$Exception->getMessage().") at "
206 .$Location.". TRACE: ".$TraceString." URL: ".$this->FullUrl();
207 $this->LogError(self::LOGLVL_ERROR, $LogMsg);
208 }
225 public static function AddObjectDirectory(
226 $Dir, $Prefix = "", $ClassPattern = NULL, $ClassReplacement = NULL)
227 {
228 # make sure directory has trailing slash
229 $Dir = $Dir.((substr($Dir, -1) != "/") ? "/" : "");
230
231 # add directory to directory list
232 self::$ObjectDirectories[$Dir] = array(
233 "Prefix" => $Prefix,
234 "ClassPattern" => $ClassPattern,
235 "ClassReplacement" => $ClassReplacement,
236 );
237 }
238
258 public function AddImageDirectories(
259 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
260 {
261 # add directories to existing image directory list
262 $this->ImageDirList = $this->AddToDirList(
263 $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
264 }
265
286 public function AddIncludeDirectories(
287 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
288 {
289 # add directories to existing image directory list
290 $this->IncludeDirList = $this->AddToDirList(
291 $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
292 }
293
313 public function AddInterfaceDirectories(
314 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
315 {
316 # add directories to existing image directory list
317 $this->InterfaceDirList = $this->AddToDirList(
318 $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
319 }
320
340 public function AddFunctionDirectories(
341 $Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
342 {
343 # add directories to existing image directory list
344 $this->FunctionDirList = $this->AddToDirList(
345 $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
346 }
347
353 public function SetBrowserDetectionFunc($DetectionFunc)
354 {
355 $this->BrowserDetectFunc = $DetectionFunc;
356 }
357
364 public function AddUnbufferedCallback($Callback, $Parameters=array())
365 {
366 if (is_callable($Callback))
367 {
368 $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
369 }
370 }
371
378 public function TemplateLocationCacheExpirationInterval($NewInterval = DB_NOVALUE)
379 {
380 return $this->UpdateSetting("TemplateLocationCacheInterval", $NewInterval);
381 }
382
386 public function ClearTemplateLocationCache()
387 {
388 $this->TemplateLocationCache = array();
389 $this->SaveTemplateLocationCache = TRUE;
390 }
391
398 public function ObjectLocationCacheExpirationInterval($NewInterval = DB_NOVALUE)
399 {
400 return $this->UpdateSetting("ObjectLocationCacheInterval", $NewInterval);
401 }
402
406 public function ClearObjectLocationCache()
407 {
408 self::$ObjectLocationCache = array();
409 self::$SaveObjectLocationCache = TRUE;
410 }
411
418 public function UrlFingerprintingEnabled($NewValue = DB_NOVALUE)
419 {
420 return $this->UpdateSetting("UrlFingerprintingEnabled", $NewValue);
421 }
422
430 public function ScssSupportEnabled($NewValue = DB_NOVALUE)
431 {
432 return $this->UpdateSetting("ScssSupportEnabled", $NewValue);
433 }
434
443 public function GenerateCompactCss($NewValue = DB_NOVALUE)
444 {
445 return $this->UpdateSetting("GenerateCompactCss", $NewValue);
446 }
447
456 public function UseMinimizedJavascript($NewValue = DB_NOVALUE)
457 {
458 return $this->UpdateSetting("UseMinimizedJavascript", $NewValue);
459 }
460
469 public function JavascriptMinimizationEnabled($NewValue = DB_NOVALUE)
470 {
471 return $this->UpdateSetting("JavascriptMinimizationEnabled", $NewValue);
472 }
473
487 public function RecordContextInCaseOfCrash(
488 $BacktraceOptions = 0, $BacktraceLimit = 0)
489 {
490 if (version_compare(PHP_VERSION, "5.4.0", ">="))
491 {
492 $this->SavedContext = debug_backtrace(
493 $BacktraceOptions, $BacktraceLimit);
494 }
495 else
496 {
497 $this->SavedContext = debug_backtrace($BacktraceOptions);
498 }
499 array_shift($this->SavedContext);
500 }
501
506 public function LoadPage($PageName)
507 {
508 # perform any clean URL rewriting
509 $PageName = $this->RewriteCleanUrls($PageName);
510
511 # sanitize incoming page name and save local copy
512 $PageName = preg_replace("/[^a-zA-Z0-9_.-]/", "", $PageName);
513 $this->PageName = $PageName;
514
515 # if page caching is turned on
516 if ($this->PageCacheEnabled())
517 {
518 # if we have a cached page
519 $CachedPage = $this->CheckForCachedPage($PageName);
520 if ($CachedPage !== NULL)
521 {
522 # set header to indicate cache hit was found
523 header("X-ScoutAF-Cache: HIT");
524
525 # display cached page and exit
526 print $CachedPage;
527 return;
528 }
529 else
530 {
531 # set header to indicate no cache hit was found
532 header("X-ScoutAF-Cache: MISS");
533 }
534 }
535
536 # buffer any output from includes or PHP file
537 ob_start();
538
539 # include any files needed to set up execution environment
540 foreach ($this->EnvIncludes as $IncludeFile)
541 {
542 include($IncludeFile);
543 }
544
545 # signal page load
546 $this->SignalEvent("EVENT_PAGE_LOAD", array("PageName" => $PageName));
547
548 # signal PHP file load
549 $SignalResult = $this->SignalEvent("EVENT_PHP_FILE_LOAD", array(
550 "PageName" => $PageName));
551
552 # if signal handler returned new page name value
553 $NewPageName = $PageName;
554 if (($SignalResult["PageName"] != $PageName)
555 && strlen($SignalResult["PageName"]))
556 {
557 # if new page name value is page file
558 if (file_exists($SignalResult["PageName"]))
559 {
560 # use new value for PHP file name
561 $PageFile = $SignalResult["PageName"];
562 }
563 else
564 {
565 # use new value for page name
566 $NewPageName = $SignalResult["PageName"];
567 }
568
569 # update local copy of page name
570 $this->PageName = $NewPageName;
571 }
572
573 # if we do not already have a PHP file
574 if (!isset($PageFile))
575 {
576 # look for PHP file for page
577 $OurPageFile = "pages/".$NewPageName.".php";
578 $LocalPageFile = "local/pages/".$NewPageName.".php";
579 $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
580 : (file_exists($OurPageFile) ? $OurPageFile
581 : "pages/".$this->DefaultPage.".php");
582 }
583
584 # load PHP file
585 include($PageFile);
586
587 # save buffered output to be displayed later after HTML file loads
588 $PageOutput = ob_get_contents();
589 ob_end_clean();
590
591 # signal PHP file load is complete
592 ob_start();
593 $Context["Variables"] = get_defined_vars();
594 $this->SignalEvent("EVENT_PHP_FILE_LOAD_COMPLETE",
595 array("PageName" => $PageName, "Context" => $Context));
596 $PageCompleteOutput = ob_get_contents();
597 ob_end_clean();
598
599 # set up for possible TSR (Terminate and Stay Resident :))
600 $ShouldTSR = $this->PrepForTSR();
601
602 # if PHP file indicated we should autorefresh to somewhere else
603 if (($this->JumpToPage) && ($this->JumpToPageDelay == 0))
604 {
605 if (!strlen(trim($PageOutput)))
606 {
607 # if client supports HTTP/1.1, use a 303 as it is most accurate
608 if ($_SERVER["SERVER_PROTOCOL"] == "HTTP/1.1")
609 {
610 header("HTTP/1.1 303 See Other");
611 header("Location: ".$this->JumpToPage);
612 }
613 else
614 {
615 # if the request was an HTTP/1.0 GET or HEAD, then
616 # use a 302 response code.
617
618 # NB: both RFC 2616 (HTTP/1.1) and RFC1945 (HTTP/1.0)
619 # explicitly prohibit automatic redirection via a 302
620 # if the request was not GET or HEAD.
621 if ($_SERVER["SERVER_PROTOCOL"] == "HTTP/1.0" &&
622 ($_SERVER["REQUEST_METHOD"] == "GET" ||
623 $_SERVER["REQUEST_METHOD"] == "HEAD") )
624 {
625 header("HTTP/1.0 302 Found");
626 header("Location: ".$this->JumpToPage);
627 }
628
629 # otherwise, fall back to a meta refresh
630 else
631 {
632 print '<html><head><meta http-equiv="refresh" '
633 .'content="0; URL='.$this->JumpToPage.'">'
634 .'</head><body></body></html>';
635 }
636 }
637 }
638 }
639 # else if HTML loading is not suppressed
640 elseif (!$this->SuppressHTML)
641 {
642 # set content-type to get rid of diacritic errors
643 header("Content-Type: text/html; charset="
644 .$this->HtmlCharset, TRUE);
645
646 # load common HTML file (defines common functions) if available
647 $CommonHtmlFile = $this->FindFile($this->IncludeDirList,
648 "Common", array("tpl", "html"));
649 if ($CommonHtmlFile) { include($CommonHtmlFile); }
650
651 # load UI functions
652 $this->LoadUIFunctions();
653
654 # begin buffering content
655 ob_start();
656
657 # signal HTML file load
658 $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD", array(
659 "PageName" => $PageName));
660
661 # if signal handler returned new page name value
662 $NewPageName = $PageName;
663 $PageContentFile = NULL;
664 if (($SignalResult["PageName"] != $PageName)
665 && strlen($SignalResult["PageName"]))
666 {
667 # if new page name value is HTML file
668 if (file_exists($SignalResult["PageName"]))
669 {
670 # use new value for HTML file name
671 $PageContentFile = $SignalResult["PageName"];
672 }
673 else
674 {
675 # use new value for page name
676 $NewPageName = $SignalResult["PageName"];
677 }
678 }
679
680 # load page content HTML file if available
681 if ($PageContentFile === NULL)
682 {
683 $PageContentFile = $this->FindFile(
684 $this->InterfaceDirList, $NewPageName,
685 array("tpl", "html"));
686 }
687 if ($PageContentFile)
688 {
689 include($PageContentFile);
690 }
691 else
692 {
693 print "<h2>ERROR: No HTML/TPL template found"
694 ." for this page (".$NewPageName.").</h2>";
695 }
696
697 # signal HTML file load complete
698 $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD_COMPLETE");
699
700 # stop buffering and save output
701 $PageContentOutput = ob_get_contents();
702 ob_end_clean();
703
704 # load page start HTML file if available
705 ob_start();
706 $PageStartFile = $this->FindFile($this->IncludeDirList, "Start",
707 array("tpl", "html"), array("StdPage", "StandardPage"));
708 if ($PageStartFile) { include($PageStartFile); }
709 $PageStartOutput = ob_get_contents();
710 ob_end_clean();
711
712 # if page auto-refresh requested
713 if ($this->JumpToPage)
714 {
715 $RefreshLine = '<meta http-equiv="refresh" content="'
716 .$this->JumpToPageDelay.'; url='.$this->JumpToPage.'">';
717 $PageStartOutput = str_replace("<head>", "<head>\n".$RefreshLine,
718 $PageStartOutput);
719 }
720
721 # load page end HTML file if available
722 ob_start();
723 $PageEndFile = $this->FindFile($this->IncludeDirList, "End",
724 array("tpl", "html"), array("StdPage", "StandardPage"));
725 if ($PageEndFile) { include($PageEndFile); }
726 $PageEndOutput = ob_get_contents();
727 ob_end_clean();
728
729 # get list of any required files not loaded
730 $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
731
732 # if a browser detection function has been made available
733 if (is_callable($this->BrowserDetectFunc))
734 {
735 # call function to get browser list
736 $Browsers = call_user_func($this->BrowserDetectFunc);
737
738 # for each required file
739 $NewRequiredFiles = array();
740 foreach ($RequiredFiles as $File)
741 {
742 # if file name includes browser keyword
743 if (preg_match("/%BROWSER%/", $File))
744 {
745 # for each browser
746 foreach ($Browsers as $Browser)
747 {
748 # substitute in browser name and add to new file list
749 $NewRequiredFiles[] = preg_replace(
750 "/%BROWSER%/", $Browser, $File);
751 }
752 }
753 else
754 {
755 # add to new file list
756 $NewRequiredFiles[] = $File;
757 }
758 }
759 $RequiredFiles = $NewRequiredFiles;
760 }
761 else
762 {
763 # filter out any files with browser keyword in their name
764 $NewRequiredFiles = array();
765 foreach ($RequiredFiles as $File)
766 {
767 if (!preg_match("/%BROWSER%/", $File))
768 {
769 $NewRequiredFiles[] = $File;
770 }
771 }
772 $RequiredFiles = $NewRequiredFiles;
773 }
774
775 # for each required file
776 foreach ($RequiredFiles as $File)
777 {
778 # locate specific file to use
779 $FilePath = $this->GUIFile($File);
780
781 # if file was found
782 if ($FilePath)
783 {
784 # generate tag for file
785 $Tag = $this->GetUIFileLoadingTag($FilePath);
786
787 # add file to HTML output based on file type
788 $FileType = $this->GetFileType($FilePath);
789 switch ($FileType)
790 {
791 case self::FT_CSS:
792 $PageStartOutput = preg_replace(
793 "#</head>#i", $Tag."\n</head>", $PageStartOutput, 1);
794 break;
795
796 case self::FT_JAVASCRIPT:
797 $PageEndOutput = preg_replace(
798 "#</body>#i", $Tag."\n</body>", $PageEndOutput, 1);
799 break;
800 }
801 }
802 }
803
804 # assemble full page
805 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
806
807 # perform any regular expression replacements in output
808 $NewFullPageOutput = preg_replace($this->OutputModificationPatterns,
809 $this->OutputModificationReplacements, $FullPageOutput);
810
811 # check to make sure replacements didn't fail
812 $FullPageOutput = $this->CheckOutputModification(
813 $FullPageOutput, $NewFullPageOutput,
814 "regular expression replacements");
815
816 # for each registered output modification callback
817 foreach ($this->OutputModificationCallbacks as $Info)
818 {
819 # set up data for callback
820 $this->OutputModificationCallbackInfo = $Info;
821
822 # perform output modification
823 $NewFullPageOutput = preg_replace_callback($Info["SearchPattern"],
824 array($this, "OutputModificationCallbackShell"),
825 $FullPageOutput);
826
827 # check to make sure modification didn't fail
828 $ErrorInfo = "callback info: ".print_r($Info, TRUE);
829 $FullPageOutput = $this->CheckOutputModification(
830 $FullPageOutput, $NewFullPageOutput, $ErrorInfo);
831 }
832
833 # provide the opportunity to modify full page output
834 $SignalResult = $this->SignalEvent("EVENT_PAGE_OUTPUT_FILTER", array(
835 "PageOutput" => $FullPageOutput));
836 if (isset($SignalResult["PageOutput"])
837 && strlen(trim($SignalResult["PageOutput"])))
838 {
839 $FullPageOutput = $SignalResult["PageOutput"];
840 }
841
842 # if relative paths may not work because we were invoked via clean URL
843 if ($this->CleanUrlRewritePerformed || self::WasUrlRewritten())
844 {
845 # if using the <base> tag is okay
846 $BaseUrl = $this->BaseUrl();
847 if ($this->UseBaseTag)
848 {
849 # add <base> tag to header
850 $PageStartOutput = str_replace("<head>",
851 "<head><base href=\"".$BaseUrl."\" />",
852 $PageStartOutput);
853
854 # re-assemble full page with new header
855 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
856
857 # the absolute URL to the current page
858 $FullUrl = $BaseUrl . $this->GetPageLocation();
859
860 # make HREF attribute values with just a fragment ID
861 # absolute since they don't work with the <base> tag because
862 # they are relative to the current page/URL, not the site
863 # root
864 $NewFullPageOutput = preg_replace(
865 array("%href=\"(#[^:\" ]+)\"%i", "%href='(#[^:' ]+)'%i"),
866 array("href=\"".$FullUrl."$1\"", "href='".$FullUrl."$1'"),
867 $FullPageOutput);
868
869 # check to make sure HREF cleanup didn't fail
870 $FullPageOutput = $this->CheckOutputModification(
871 $FullPageOutput, $NewFullPageOutput,
872 "HREF cleanup");
873 }
874 else
875 {
876 # try to fix any relative paths throughout code
877 $RelativePathPatterns = array(
878 "%src=\"/?([^?*:;{}\\\\\" ]+)\.(js|css|gif|png|jpg)\"%i",
879 "%src='/?([^?*:;{}\\\\' ]+)\.(js|css|gif|png|jpg)'%i",
880 # don't rewrite HREF attributes that are just
881 # fragment IDs because they are relative to the
882 # current page/URL, not the site root
883 "%href=\"/?([^#][^:\" ]*)\"%i",
884 "%href='/?([^#][^:' ]*)'%i",
885 "%action=\"/?([^#][^:\" ]*)\"%i",
886 "%action='/?([^#][^:' ]*)'%i",
887 "%@import\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
888 "%@import\s+url\('/?([^:\" ]+)'\s*\)%i",
889 "%src:\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
890 "%src:\s+url\('/?([^:\" ]+)'\s*\)%i",
891 "%@import\s+\"/?([^:\" ]+)\"\s*%i",
892 "%@import\s+'/?([^:\" ]+)'\s*%i",
893 );
894 $RelativePathReplacements = array(
895 "src=\"".$BaseUrl."$1.$2\"",
896 "src=\"".$BaseUrl."$1.$2\"",
897 "href=\"".$BaseUrl."$1\"",
898 "href=\"".$BaseUrl."$1\"",
899 "action=\"".$BaseUrl."$1\"",
900 "action=\"".$BaseUrl."$1\"",
901 "@import url(\"".$BaseUrl."$1\")",
902 "@import url('".$BaseUrl."$1')",
903 "src: url(\"".$BaseUrl."$1\")",
904 "src: url('".$BaseUrl."$1')",
905 "@import \"".$BaseUrl."$1\"",
906 "@import '".$BaseUrl."$1'",
907 );
908 $NewFullPageOutput = preg_replace($RelativePathPatterns,
909 $RelativePathReplacements, $FullPageOutput);
910
911 # check to make sure relative path fixes didn't fail
912 $FullPageOutput = $this->CheckOutputModification(
913 $FullPageOutput, $NewFullPageOutput,
914 "relative path fixes");
915 }
916 }
917
918 # handle any necessary alternate domain rewriting
919 $FullPageOutput = $this->RewriteAlternateDomainUrls($FullPageOutput);
920
921 # update page cache for this page
922 $this->UpdatePageCache($PageName, $FullPageOutput);
923
924 # write out full page
925 print $FullPageOutput;
926 }
927
928 # run any post-processing routines
929 foreach ($this->PostProcessingFuncs as $Func)
930 {
931 call_user_func_array($Func["FunctionName"], $Func["Arguments"]);
932 }
933
934 # write out any output buffered from page code execution
935 if (strlen($PageOutput))
936 {
937 if (!$this->SuppressHTML)
938 {
939 ?><table width="100%" cellpadding="5"
940 style="border: 2px solid #666666; background: #CCCCCC;
941 font-family: Courier New, Courier, monospace;
942 margin-top: 10px;"><tr><td><?PHP
943 }
944 if ($this->JumpToPage)
945 {
946 ?><div style="color: #666666;"><span style="font-size: 150%;">
947 <b>Page Jump Aborted</b></span>
948 (because of error or other unexpected output)<br />
949 <b>Jump Target:</b>
950 <i><?PHP print($this->JumpToPage); ?></i></div><?PHP
951 }
952 print $PageOutput;
953 if (!$this->SuppressHTML)
954 {
955 ?></td></tr></table><?PHP
956 }
957 }
958
959 # write out any output buffered from the page code execution complete signal
960 if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
961 {
962 print $PageCompleteOutput;
963 }
964
965 # log slow page loads
966 if ($this->LogSlowPageLoads()
967 && !$this->DoNotLogSlowPageLoad
968 && ($this->GetElapsedExecutionTime()
969 >= ($this->SlowPageLoadThreshold())))
970 {
971 $RemoteHost = gethostbyaddr($_SERVER["REMOTE_ADDR"]);
972 if ($RemoteHost === FALSE)
973 {
974 $RemoteHost = $_SERVER["REMOTE_ADDR"];
975 }
976 elseif ($RemoteHost != $_SERVER["REMOTE_ADDR"])
977 {
978 $RemoteHost .= " (".$_SERVER["REMOTE_ADDR"].")";
979 }
980 $SlowPageLoadMsg = "Slow page load ("
981 .intval($this->GetElapsedExecutionTime())."s) for "
982 .$this->FullUrl()." from ".$RemoteHost;
983 $this->LogMessage(self::LOGLVL_INFO, $SlowPageLoadMsg);
984 }
985
986 # execute callbacks that should not have their output buffered
987 foreach ($this->UnbufferedCallbacks as $Callback)
988 {
989 call_user_func_array($Callback[0], $Callback[1]);
990 }
991
992 # log high memory usage
993 if (function_exists("memory_get_peak_usage"))
994 {
995 $MemoryThreshold = ($this->HighMemoryUsageThreshold()
996 * $this->GetPhpMemoryLimit()) / 100;
997 if ($this->LogHighMemoryUsage()
998 && (memory_get_peak_usage() >= $MemoryThreshold))
999 {
1000 $HighMemUsageMsg = "High peak memory usage ("
1001 .intval(memory_get_peak_usage()).") for "
1002 .$this->FullUrl()." from "
1003 .$_SERVER["REMOTE_ADDR"];
1004 $this->LogMessage(self::LOGLVL_INFO, $HighMemUsageMsg);
1005 }
1006 }
1007
1008 # terminate and stay resident (TSR!) if indicated and HTML has been output
1009 # (only TSR if HTML has been output because otherwise browsers will misbehave)
1010 if ($ShouldTSR) { $this->LaunchTSR(); }
1011 }
1012
1018 public function GetPageName()
1019 {
1020 return $this->PageName;
1021 }
1022
1028 public function GetPageLocation()
1029 {
1030 # retrieve current URL
1031 $Url = self::GetScriptUrl();
1032
1033 # remove the base path if present
1034 $BasePath = $this->Settings["BasePath"];
1035 if (stripos($Url, $BasePath) === 0)
1036 {
1037 $Url = substr($Url, strlen($BasePath));
1038 }
1039
1040 # if we're being accessed via an alternate domain,
1041 # add the appropriate prefix in
1042 if ($this->HtaccessSupport() &&
1043 self::$RootUrlOverride !== NULL)
1044 {
1045 $VHost = $_SERVER["SERVER_NAME"];
1046 if (isset($this->AlternateDomainPrefixes[$VHost]))
1047 {
1048 $ThisPrefix = $this->AlternateDomainPrefixes[$VHost];
1049 $Url = $ThisPrefix."/".$Url;
1050 }
1051 }
1052
1053 return $Url;
1054 }
1055
1061 public function GetPageUrl()
1062 {
1063 return self::BaseUrl() . $this->GetPageLocation();
1064 }
1065
1077 public function SetJumpToPage($Page, $Delay = 0, $IsLiteral = FALSE)
1078 {
1079 if (!is_null($Page)
1080 && (!$IsLiteral)
1081 && (strpos($Page, "?") === FALSE)
1082 && ((strpos($Page, "=") !== FALSE)
1083 || ((stripos($Page, ".php") === FALSE)
1084 && (stripos($Page, ".htm") === FALSE)
1085 && (strpos($Page, "/") === FALSE)))
1086 && (stripos($Page, "http://") !== 0)
1087 && (stripos($Page, "https://") !== 0))
1088 {
1089 $this->JumpToPage = self::BaseUrl() . "index.php?P=".$Page;
1090 }
1091 else
1092 {
1093 $this->JumpToPage = $Page;
1094 }
1095 $this->JumpToPageDelay = $Delay;
1096 }
1097
1102 public function JumpToPageIsSet()
1103 {
1104 return ($this->JumpToPage === NULL) ? FALSE : TRUE;
1105 }
1106
1116 public function HtmlCharset($NewSetting = NULL)
1117 {
1118 if ($NewSetting !== NULL) { $this->HtmlCharset = $NewSetting; }
1119 return $this->HtmlCharset;
1120 }
1121
1131 public function DoNotMinimizeFile($File)
1132 {
1133 if (!is_array($File)) { $File = array($File); }
1134 $this->DoNotMinimizeList = array_merge($this->DoNotMinimizeList, $File);
1135 }
1136
1147 public function UseBaseTag($NewValue = NULL)
1148 {
1149 if ($NewValue !== NULL) { $this->UseBaseTag = $NewValue ? TRUE : FALSE; }
1150 return $this->UseBaseTag;
1151 }
1152
1159 public function SuppressHTMLOutput($NewSetting = TRUE)
1160 {
1161 $this->SuppressHTML = $NewSetting;
1162 }
1163
1169 public static function DefaultUserInterface($UIName = NULL)
1170 {
1171 if ($UIName !== NULL)
1172 {
1173 self::$DefaultUI = $UIName;
1174 }
1175 return self::$DefaultUI;
1176 }
1177
1184 public static function ActiveUserInterface($UIName = NULL)
1185 {
1186 if ($UIName !== NULL)
1187 {
1188 self::$ActiveUI = preg_replace("/^SPTUI--/", "", $UIName);
1189 }
1190 return self::$ActiveUI;
1191 }
1192
1203 public function GetUserInterfaces($FilterExp = NULL)
1204 {
1205 static $Interfaces;
1206
1207 if (!isset($Interfaces[$FilterExp]))
1208 {
1209 # retrieve paths to user interface directories
1210 $Paths = $this->GetUserInterfacePaths($FilterExp);
1211
1212 # start out with an empty list
1213 $Interfaces[$FilterExp] = array();
1214
1215 # for each possible UI directory
1216 foreach ($Paths as $CanonicalName => $Path)
1217 {
1218 # if name file available
1219 $LabelFile = $Path."/NAME";
1220 if (is_readable($LabelFile))
1221 {
1222 # read the UI name
1223 $Label = file_get_contents($LabelFile);
1224
1225 # if the UI name looks reasonable
1226 if (strlen(trim($Label)))
1227 {
1228 # use read name
1229 $Interfaces[$FilterExp][$CanonicalName] = $Label;
1230 }
1231 }
1232
1233 # if we do not have a name yet
1234 if (!isset($Interfaces[$FilterExp][$CanonicalName]))
1235 {
1236 # use base directory for name
1237 $Interfaces[$FilterExp][$CanonicalName] = basename($Path);
1238 }
1239 }
1240 }
1241
1242 # return list to caller
1243 return $Interfaces[$FilterExp];
1244 }
1245
1254 public function GetUserInterfacePaths($FilterExp = NULL)
1255 {
1256 static $InterfacePaths;
1257
1258 if (!isset($InterfacePaths[$FilterExp]))
1259 {
1260 # extract possible UI directories from interface directory list
1261 $InterfaceDirs = array();
1262 foreach ($this->InterfaceDirList as $Dir)
1263 {
1264 $Matches = array();
1265 if (preg_match("#([a-zA-Z0-9/]*interface)/[a-zA-Z0-9%/]*#",
1266 $Dir, $Matches))
1267 {
1268 $Dir = $Matches[1];
1269 if (!in_array($Dir, $InterfaceDirs))
1270 {
1271 $InterfaceDirs[] = $Dir;
1272 }
1273 }
1274 }
1275
1276 # reverse order of interface directories so that the directory
1277 # returned is the base directory for the interface
1278 $InterfaceDirs = array_reverse($InterfaceDirs);
1279
1280 # start out with an empty list
1281 $InterfacePaths[$FilterExp] = array();
1282 $InterfacesFound = array();
1283
1284 # for each possible UI directory
1285 foreach ($InterfaceDirs as $InterfaceDir)
1286 {
1287 $Dir = dir($InterfaceDir);
1288
1289 # for each file in current directory
1290 while (($DirEntry = $Dir->read()) !== FALSE)
1291 {
1292 $InterfacePath = $InterfaceDir."/".$DirEntry;
1293
1294 # skip anything we have already found
1295 # or that doesn't have a name in the required format
1296 # or that isn't a directory
1297 # or that doesn't match the filter regex (if supplied)
1298 if (in_array($DirEntry, $InterfacesFound)
1299 || !preg_match('/^[a-zA-Z0-9]+$/', $DirEntry)
1300 || !is_dir($InterfacePath)
1301 || (($FilterExp !== NULL)
1302 && !preg_match($FilterExp, $InterfacePath)))
1303 {
1304 continue;
1305 }
1306
1307 # add interface to list
1308 $InterfacePaths[$FilterExp][$DirEntry] = $InterfacePath;
1309 $InterfacesFound[] = $DirEntry;
1310 }
1311
1312 $Dir->close();
1313 }
1314 }
1315
1316 # return list to caller
1317 return $InterfacePaths[$FilterExp];
1318 }
1319
1344 public function AddPostProcessingCall($FunctionName,
1345 &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
1346 &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
1347 &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
1348 {
1349 $FuncIndex = count($this->PostProcessingFuncs);
1350 $this->PostProcessingFuncs[$FuncIndex]["FunctionName"] = $FunctionName;
1351 $this->PostProcessingFuncs[$FuncIndex]["Arguments"] = array();
1352 $Index = 1;
1353 while (isset(${"Arg".$Index}) && (${"Arg".$Index} !== self::NOVALUE))
1354 {
1355 $this->PostProcessingFuncs[$FuncIndex]["Arguments"][$Index]
1356 =& ${"Arg".$Index};
1357 $Index++;
1358 }
1359 }
1360
1366 public function AddEnvInclude($FileName)
1367 {
1368 $this->EnvIncludes[] = $FileName;
1369 }
1370
1377 public function GUIFile($FileName)
1378 {
1379 # determine which location to search based on file suffix
1380 $FileType = $this->GetFileType($FileName);
1381 $DirList = ($FileType == self::FT_IMAGE)
1382 ? $this->ImageDirList : $this->IncludeDirList;
1383
1384 # if directed to use minimized JavaScript file
1385 if (($FileType == self::FT_JAVASCRIPT) && $this->UseMinimizedJavascript())
1386 {
1387 # look for minimized version of file
1388 $MinimizedFileName = substr_replace($FileName, ".min", -3, 0);
1389 $FoundFileName = $this->FindFile($DirList, $MinimizedFileName);
1390
1391 # if minimized file was not found
1392 if (is_null($FoundFileName))
1393 {
1394 # look for unminimized file
1395 $FoundFileName = $this->FindFile($DirList, $FileName);
1396
1397 # if unminimized file found
1398 if (!is_null($FoundFileName))
1399 {
1400 # if minimization enabled and supported
1401 if ($this->JavascriptMinimizationEnabled()
1402 && self::JsMinRewriteSupport())
1403 {
1404 # attempt to create minimized file
1405 $MinFileName = $this->MinimizeJavascriptFile(
1406 $FoundFileName);
1407
1408 # if minimization succeeded
1409 if ($MinFileName !== NULL)
1410 {
1411 # use minimized version
1412 $FoundFileName = $MinFileName;
1413
1414 # save file modification time if needed for fingerprinting
1415 if ($this->UrlFingerprintingEnabled())
1416 {
1417 $FileMTime = filemtime($FoundFileName);
1418 }
1419
1420 # strip off the cache location, allowing .htaccess
1421 # to handle that for us
1422 $FoundFileName = str_replace(
1423 self::$JSMinCacheDir."/", "", $FoundFileName);
1424 }
1425 }
1426 }
1427 }
1428 }
1429 # else if directed to use SCSS files
1430 elseif (($FileType == self::FT_CSS) && $this->ScssSupportEnabled())
1431 {
1432 # look for SCSS version of file
1433 $SourceFileName = preg_replace("/.css$/", ".scss", $FileName);
1434 $FoundSourceFileName = $this->FindFile($DirList, $SourceFileName);
1435
1436 # if SCSS file not found
1437 if ($FoundSourceFileName === NULL)
1438 {
1439 # look for CSS file
1440 $FoundFileName = $this->FindFile($DirList, $FileName);
1441 }
1442 else
1443 {
1444 # compile SCSS file (if updated) and return resulting CSS file
1445 $FoundFileName = $this->CompileScssFile($FoundSourceFileName);
1446
1447 # save file modification time if needed for fingerprinting
1448 if ($this->UrlFingerprintingEnabled())
1449 {
1450 $FileMTime = filemtime($FoundFileName);
1451 }
1452
1453 # strip off the cache location, allowing .htaccess to handle that for us
1454 if (self::ScssRewriteSupport())
1455 {
1456 $FoundFileName = str_replace(
1457 self::$ScssCacheDir."/", "", $FoundFileName);
1458 }
1459 }
1460 }
1461 # otherwise just search for the file
1462 else
1463 {
1464 $FoundFileName = $this->FindFile($DirList, $FileName);
1465 }
1466
1467 # add non-image files to list of found files (used for required files loading)
1468 if ($FileType != self::FT_IMAGE)
1469 { $this->FoundUIFiles[] = basename($FoundFileName); }
1470
1471 # if UI file fingerprinting is enabled and supported
1472 if ($this->UrlFingerprintingEnabled()
1473 && self::UrlFingerprintingRewriteSupport()
1474 && (isset($FileMTime) || file_exists($FoundFileName)))
1475 {
1476 # if file does not appear to be a server-side inclusion
1477 if (!preg_match('/\.(html|php)$/i', $FoundFileName))
1478 {
1479 # for each URL fingerprinting blacklist entry
1480 $OnBlacklist = FALSE;
1481 foreach ($this->UrlFingerprintBlacklist as $BlacklistEntry)
1482 {
1483 # if entry looks like a regular expression pattern
1484 if ($BlacklistEntry[0] == substr($BlacklistEntry, -1))
1485 {
1486 # check file name against regular expression
1487 if (preg_match($BlacklistEntry, $FoundFileName))
1488 {
1489 $OnBlacklist = TRUE;
1490 break;
1491 }
1492 }
1493 else
1494 {
1495 # check file name directly against entry
1496 if (basename($FoundFileName) == $BlacklistEntry)
1497 {
1498 $OnBlacklist = TRUE;
1499 break;
1500 }
1501 }
1502 }
1503
1504 # if file was not on blacklist
1505 if (!$OnBlacklist)
1506 {
1507 # get file modification time if not already retrieved
1508 if (!isset($FileMTime))
1509 {
1510 $FileMTime = filemtime($FoundFileName);
1511 }
1512
1513 # add timestamp fingerprint to file name
1514 $Fingerprint = sprintf("%06X",
1515 ($FileMTime % 0xFFFFFF));
1516 $FoundFileName = preg_replace("/^(.+)\.([a-z]+)$/",
1517 "$1.".$Fingerprint.".$2",
1518 $FoundFileName);
1519 }
1520 }
1521 }
1522
1523 # return file name to caller
1524 return $FoundFileName;
1525 }
1526
1535 public function PUIFile($FileName)
1536 {
1537 $FullFileName = $this->GUIFile($FileName);
1538 if ($FullFileName) { print($FullFileName); }
1539 }
1540
1555 public function IncludeUIFile($FileNames, $AdditionalAttributes = NULL)
1556 {
1557 # convert file name to array if necessary
1558 if (!is_array($FileNames)) { $FileNames = array($FileNames); }
1559
1560 # pad additional attributes if supplied
1561 $AddAttribs = $AdditionalAttributes ? " ".$AdditionalAttributes : "";
1562
1563 # for each file
1564 foreach ($FileNames as $BaseFileName)
1565 {
1566 # retrieve full file name
1567 $FileName = $this->GUIFile($BaseFileName);
1568
1569 # if file was found
1570 if ($FileName)
1571 {
1572 # print appropriate tag
1573 print $this->GetUIFileLoadingTag(
1574 $FileName, $AdditionalAttributes);
1575 }
1576
1577 # if we are not already loading an override file
1578 if (!preg_match("/-Override.(css|scss|js)$/", $BaseFileName))
1579 {
1580 # attempt to load override file if available
1581 $FileType = $this->GetFileType($BaseFileName);
1582 switch ($FileType)
1583 {
1584 case self::FT_CSS:
1585 $OverrideFileName = preg_replace(
1586 "/\.(css|scss)$/", "-Override.$1",
1587 $BaseFileName);
1588 $this->IncludeUIFile($OverrideFileName,
1589 $AdditionalAttributes);
1590 break;
1591
1592 case self::FT_JAVASCRIPT:
1593 $OverrideFileName = preg_replace(
1594 "/\.js$/", "-Override.js",
1595 $BaseFileName);
1596 $this->IncludeUIFile($OverrideFileName,
1597 $AdditionalAttributes);
1598 break;
1599 }
1600 }
1601 }
1602 }
1603
1610 public function DoNotUrlFingerprint($Pattern)
1611 {
1612 $this->UrlFingerprintBlacklist[] = $Pattern;
1613 }
1614
1622 public function RequireUIFile($FileName)
1623 {
1624 $this->AdditionalRequiredUIFiles[] = $FileName;
1625 }
1626
1632 public static function GetFileType($FileName)
1633 {
1634 static $FileTypeCache;
1635 if (isset($FileTypeCache[$FileName]))
1636 {
1637 return $FileTypeCache[$FileName];
1638 }
1639
1640 $FileSuffix = strtolower(substr($FileName, -3));
1641 if ($FileSuffix == "css")
1642 {
1643 $FileTypeCache[$FileName] = self::FT_CSS;
1644 }
1645 elseif ($FileSuffix == ".js")
1646 {
1647 $FileTypeCache[$FileName] = self::FT_JAVASCRIPT;
1648 }
1649 elseif (($FileSuffix == "gif")
1650 || ($FileSuffix == "jpg")
1651 || ($FileSuffix == "png"))
1652 {
1653 $FileTypeCache[$FileName] = self::FT_IMAGE;
1654 }
1655 else
1656 {
1657 $FileTypeCache[$FileName] = self::FT_OTHER;
1658 }
1659
1660 return $FileTypeCache[$FileName];
1661 }
1663 const FT_OTHER = 0;
1665 const FT_CSS = 1;
1667 const FT_IMAGE = 2;
1669 const FT_JAVASCRIPT = 3;
1670
1679 public function LoadFunction($Callback)
1680 {
1681 # if specified function is not currently available
1682 if (!is_callable($Callback))
1683 {
1684 # if function info looks legal
1685 if (is_string($Callback) && strlen($Callback))
1686 {
1687 # start with function directory list
1688 $Locations = $this->FunctionDirList;
1689
1690 # add object directories to list
1691 $Locations = array_merge(
1692 $Locations, array_keys(self::$ObjectDirectories));
1693
1694 # look for function file
1695 $FunctionFileName = $this->FindFile($Locations, "F-".$Callback,
1696 array("php", "html"));
1697
1698 # if function file was found
1699 if ($FunctionFileName)
1700 {
1701 # load function file
1702 include_once($FunctionFileName);
1703 }
1704 else
1705 {
1706 # log error indicating function load failed
1707 $this->LogError(self::LOGLVL_ERROR, "Unable to load function"
1708 ." for callback \"".$Callback."\".");
1709 }
1710 }
1711 else
1712 {
1713 # log error indicating specified function info was bad
1714 $this->LogError(self::LOGLVL_ERROR, "Unloadable callback value"
1715 ." (".$Callback.")"
1716 ." passed to AF::LoadFunction() by "
1717 .StdLib::GetMyCaller().".");
1718 }
1719 }
1720
1721 # report to caller whether function load succeeded
1722 return is_callable($Callback);
1723 }
1724
1729 public function GetElapsedExecutionTime()
1730 {
1731 return microtime(TRUE) - $this->ExecutionStartTime;
1732 }
1733
1738 public function GetSecondsBeforeTimeout()
1739 {
1740 return $this->MaxExecutionTime() - $this->GetElapsedExecutionTime();
1741 }
1742
1743 /*@)*/ /* Application Framework */
1744
1745
1746 # ---- Page Caching ------------------------------------------------------
1747 /*@(*/
1749
1756 public function PageCacheEnabled($NewValue = DB_NOVALUE)
1757 {
1758 return $this->UpdateSetting("PageCacheEnabled", $NewValue);
1759 }
1760
1767 public function PageCacheExpirationPeriod($NewValue = DB_NOVALUE)
1768 {
1769 return $this->UpdateSetting("PageCacheExpirationPeriod", $NewValue);
1770 }
1771
1776 public function DoNotCacheCurrentPage()
1777 {
1778 $this->CacheCurrentPage = FALSE;
1779 }
1780
1787 public function AddPageCacheTag($Tag, $Pages = NULL)
1788 {
1789 # normalize tag
1790 $Tag = strtolower($Tag);
1791
1792 # if pages were supplied
1793 if ($Pages !== NULL)
1794 {
1795 # add pages to list for this tag
1796 if (isset($this->PageCacheTags[$Tag]))
1797 {
1798 $this->PageCacheTags[$Tag] = array_merge(
1799 $this->PageCacheTags[$Tag], $Pages);
1800 }
1801 else
1802 {
1803 $this->PageCacheTags[$Tag] = $Pages;
1804 }
1805 }
1806 else
1807 {
1808 # add current page to list for this tag
1809 $this->PageCacheTags[$Tag][] = "CURRENT";
1810 }
1811 }
1812
1818 public function ClearPageCacheForTag($Tag)
1819 {
1820 # get tag ID
1821 $TagId = $this->GetPageCacheTagId($Tag);
1822
1823 # delete pages and tag/page connections for specified tag
1824 $this->DB->Query("DELETE CP, CPTI"
1825 ." FROM AF_CachedPages CP, AF_CachedPageTagInts CPTI"
1826 ." WHERE CPTI.TagId = ".intval($TagId)
1827 ." AND CP.CacheId = CPTI.CacheId");
1828 }
1829
1833 public function ClearPageCache()
1834 {
1835 # clear all page cache tables
1836 $this->DB->Query("TRUNCATE TABLE AF_CachedPages");
1837 $this->DB->Query("TRUNCATE TABLE AF_CachedPageTags");
1838 $this->DB->Query("TRUNCATE TABLE AF_CachedPageTagInts");
1839 }
1840
1847 public function GetPageCacheInfo()
1848 {
1849 $Length = $this->DB->Query("SELECT COUNT(*) AS CacheLen"
1850 ." FROM AF_CachedPages", "CacheLen");
1851 $Oldest = $this->DB->Query("SELECT CachedAt FROM AF_CachedPages"
1852 ." ORDER BY CachedAt ASC LIMIT 1", "CachedAt");
1853 return array(
1854 "NumberOfEntries" => $Length,
1855 "OldestTimestamp" => strtotime($Oldest),
1856 );
1857 }
1858
1859 /*@)*/ /* Page Caching */
1860
1861
1862 # ---- Logging -----------------------------------------------------------
1863 /*@(*/
1865
1879 public function LogSlowPageLoads(
1880 $NewValue = DB_NOVALUE, $Persistent = FALSE)
1881 {
1882 return $this->UpdateSetting(
1883 "LogSlowPageLoads", $NewValue, $Persistent);
1884 }
1885
1896 public function SlowPageLoadThreshold(
1897 $NewValue = DB_NOVALUE, $Persistent = FALSE)
1898 {
1899 return $this->UpdateSetting(
1900 "SlowPageLoadThreshold", $NewValue, $Persistent);
1901 }
1902
1916 public function LogHighMemoryUsage(
1917 $NewValue = DB_NOVALUE, $Persistent = FALSE)
1918 {
1919 return $this->UpdateSetting(
1920 "LogHighMemoryUsage", $NewValue, $Persistent);
1921 }
1922
1934 public function HighMemoryUsageThreshold(
1935 $NewValue = DB_NOVALUE, $Persistent = FALSE)
1936 {
1937 return $this->UpdateSetting(
1938 "HighMemoryUsageThreshold", $NewValue, $Persistent);
1939 }
1940
1954 public function LogError($Level, $Msg)
1955 {
1956 # if error level is at or below current logging level
1957 if ($this->Settings["LoggingLevel"] >= $Level)
1958 {
1959 # attempt to log error message
1960 $Result = $this->LogMessage($Level, $Msg);
1961
1962 # if logging attempt failed and level indicated significant error
1963 if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
1964 {
1965 # throw exception about inability to log error
1966 static $AlreadyThrewException = FALSE;
1967 if (!$AlreadyThrewException)
1968 {
1969 $AlreadyThrewException = TRUE;
1970 throw new Exception("Unable to log error (".$Level.": ".$Msg
1971 .") to ".$this->LogFileName);
1972 }
1973 }
1974
1975 # report to caller whether message was logged
1976 return $Result;
1977 }
1978 else
1979 {
1980 # report to caller that message was not logged
1981 return FALSE;
1982 }
1983 }
1984
1996 public function LogMessage($Level, $Msg)
1997 {
1998 # if message level is at or below current logging level
1999 if ($this->Settings["LoggingLevel"] >= $Level)
2000 {
2001 # attempt to open log file
2002 $FHndl = @fopen($this->LogFileName, "a");
2003
2004 # if log file could not be open
2005 if ($FHndl === FALSE)
2006 {
2007 # report to caller that message was not logged
2008 return FALSE;
2009 }
2010 else
2011 {
2012 # format log entry
2013 $ErrorAbbrevs = array(
2014 self::LOGLVL_FATAL => "FTL",
2015 self::LOGLVL_ERROR => "ERR",
2016 self::LOGLVL_WARNING => "WRN",
2017 self::LOGLVL_INFO => "INF",
2018 self::LOGLVL_DEBUG => "DBG",
2019 self::LOGLVL_TRACE => "TRC",
2020 );
2021 $Msg = str_replace(array("\n", "\t", "\r"), " ", $Msg);
2022 $Msg = substr(trim($Msg), 0, self::LOGFILE_MAX_LINE_LENGTH);
2023 $LogEntry = date("Y-m-d H:i:s")
2024 ." ".($this->RunningInBackground ? "B" : "F")
2025 ." ".$ErrorAbbrevs[$Level]
2026 ." ".$Msg;
2027
2028 # write entry to log
2029 $Success = fwrite($FHndl, $LogEntry."\n");
2030
2031 # close log file
2032 fclose($FHndl);
2033
2034 # report to caller whether message was logged
2035 return ($Success === FALSE) ? FALSE : TRUE;
2036 }
2037 }
2038 else
2039 {
2040 # report to caller that message was not logged
2041 return FALSE;
2042 }
2043 }
2044
2066 public function LoggingLevel($NewValue = DB_NOVALUE)
2067 {
2068 # constrain new level (if supplied) to within legal bounds
2069 if ($NewValue !== DB_NOVALUE)
2070 {
2071 $NewValue = max(min($NewValue, 6), 1);
2072 }
2073
2074 # set new logging level (if supplied) and return current level to caller
2075 return $this->UpdateSetting("LoggingLevel", $NewValue);
2076 }
2077
2084 public function LogFile($NewValue = NULL)
2085 {
2086 if ($NewValue !== NULL) { $this->LogFileName = $NewValue; }
2087 return $this->LogFileName;
2088 }
2089
2099 public function GetLogEntries($Limit = 0)
2100 {
2101 # return no entries if there isn't a log file or we can't read it
2102 if (!is_readable($this->LogFile()))
2103 {
2104 return array();
2105 }
2106
2107 # if max number of entries specified
2108 if ($Limit > 0)
2109 {
2110 # load lines from file
2111 $FHandle = fopen($this->LogFile(), "r");
2112 $FileSize = filesize($this->LogFile());
2113 $SeekPosition = max(0,
2114 ($FileSize - (self::LOGFILE_MAX_LINE_LENGTH * ($Limit + 1))));
2115 fseek($FHandle, $SeekPosition);
2116 $Block = fread($FHandle, ($FileSize - $SeekPosition));
2117 fclose($FHandle);
2118 $Lines = explode(PHP_EOL, $Block);
2119 array_shift($Lines);
2120
2121 # prune array back to requested number of entries
2122 $Lines = array_slice($Lines, (0 - $Limit));
2123 }
2124 else
2125 {
2126 # load all lines from log file
2127 $Lines = file($this->LogFile(), FILE_IGNORE_NEW_LINES);
2128 if ($Lines === FALSE)
2129 {
2130 return array();
2131 }
2132 }
2133
2134 # reverse line order
2135 $Lines = array_reverse($Lines);
2136
2137 # for each log file line
2138 $Entries = array();
2139 foreach ($Lines as $Line)
2140 {
2141 # attempt to parse line into component parts
2142 $Pieces = explode(" ", $Line, 5);
2143 $Date = isset($Pieces[0]) ? $Pieces[0] : "";
2144 $Time = isset($Pieces[1]) ? $Pieces[1] : "";
2145 $Back = isset($Pieces[2]) ? $Pieces[2] : "";
2146 $Level = isset($Pieces[3]) ? $Pieces[3] : "";
2147 $Msg = isset($Pieces[4]) ? $Pieces[4] : "";
2148
2149 # skip line if it looks invalid
2150 $ErrorAbbrevs = array(
2151 "FTL" => self::LOGLVL_FATAL,
2152 "ERR" => self::LOGLVL_ERROR,
2153 "WRN" => self::LOGLVL_WARNING,
2154 "INF" => self::LOGLVL_INFO,
2155 "DBG" => self::LOGLVL_DEBUG,
2156 "TRC" => self::LOGLVL_TRACE,
2157 );
2158 if ((($Back != "F") && ($Back != "B"))
2159 || !array_key_exists($Level, $ErrorAbbrevs)
2160 || !strlen($Msg))
2161 {
2162 continue;
2163 }
2164
2165 # convert parts into appropriate values and add to entries
2166 $Entries[] = array(
2167 "Time" => strtotime($Date." ".$Time),
2168 "Background" => ($Back == "B") ? TRUE : FALSE,
2169 "Level" => $ErrorAbbrevs[$Level],
2170 "Message" => $Msg,
2171 );
2172 }
2173
2174 # return entries to caller
2175 return $Entries;
2176 }
2177
2182 const LOGLVL_TRACE = 6;
2187 const LOGLVL_DEBUG = 5;
2193 const LOGLVL_INFO = 4;
2198 const LOGLVL_WARNING = 3;
2204 const LOGLVL_ERROR = 2;
2209 const LOGLVL_FATAL = 1;
2210
2214 const LOGFILE_MAX_LINE_LENGTH = 2048;
2215
2216 /*@)*/ /* Logging */
2217
2218
2219 # ---- Event Handling ----------------------------------------------------
2220 /*@(*/
2222
2226 const EVENTTYPE_DEFAULT = 1;
2232 const EVENTTYPE_CHAIN = 2;
2238 const EVENTTYPE_FIRST = 3;
2246 const EVENTTYPE_NAMED = 4;
2247
2249 const ORDER_FIRST = 1;
2251 const ORDER_MIDDLE = 2;
2253 const ORDER_LAST = 3;
2254
2263 public function RegisterEvent($EventsOrEventName, $EventType = NULL)
2264 {
2265 # convert parameters to array if not already in that form
2266 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2267 : array($EventsOrEventName => $EventType);
2268
2269 # for each event
2270 foreach ($Events as $Name => $Type)
2271 {
2272 # store event information
2273 $this->RegisteredEvents[$Name]["Type"] = $Type;
2274 $this->RegisteredEvents[$Name]["Hooks"] = array();
2275 }
2276 }
2277
2284 public function IsRegisteredEvent($EventName)
2285 {
2286 return array_key_exists($EventName, $this->RegisteredEvents)
2287 ? TRUE : FALSE;
2288 }
2289
2296 public function IsHookedEvent($EventName)
2297 {
2298 # the event isn't hooked to if it isn't even registered
2299 if (!$this->IsRegisteredEvent($EventName))
2300 {
2301 return FALSE;
2302 }
2303
2304 # return TRUE if there is at least one callback hooked to the event
2305 return count($this->RegisteredEvents[$EventName]["Hooks"]) > 0;
2306 }
2307
2321 public function HookEvent(
2322 $EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
2323 {
2324 # convert parameters to array if not already in that form
2325 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2326 : array($EventsOrEventName => $Callback);
2327
2328 # for each event
2329 $Success = TRUE;
2330 foreach ($Events as $EventName => $EventCallback)
2331 {
2332 # if callback is valid
2333 if (is_callable($EventCallback))
2334 {
2335 # if this is a periodic event we process internally
2336 if (isset($this->PeriodicEvents[$EventName]))
2337 {
2338 # process event now
2339 $this->ProcessPeriodicEvent($EventName, $EventCallback);
2340 }
2341 # if specified event has been registered
2342 elseif (isset($this->RegisteredEvents[$EventName]))
2343 {
2344 # add callback for event
2345 $this->RegisteredEvents[$EventName]["Hooks"][]
2346 = array("Callback" => $EventCallback, "Order" => $Order);
2347
2348 # sort callbacks by order
2349 if (count($this->RegisteredEvents[$EventName]["Hooks"]) > 1)
2350 {
2351 usort($this->RegisteredEvents[$EventName]["Hooks"],
2352 function ($A, $B) {
2353 return StdLib::SortCompare(
2354 $A["Order"], $B["Order"]);
2355 });
2356 }
2357 }
2358 else
2359 {
2360 $Success = FALSE;
2361 }
2362 }
2363 else
2364 {
2365 $Success = FALSE;
2366 }
2367 }
2368
2369 # report to caller whether all callbacks were hooked
2370 return $Success;
2371 }
2372
2386 public function UnhookEvent(
2387 $EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
2388 {
2389 # convert parameters to array if not already in that form
2390 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
2391 : array($EventsOrEventName => $Callback);
2392
2393 # for each event
2394 $UnhookCount = 0;
2395 foreach ($Events as $EventName => $EventCallback)
2396 {
2397 # if this event has been registered and hooked
2398 if (isset($this->RegisteredEvents[$EventName])
2399 && count($this->RegisteredEvents[$EventName]))
2400 {
2401 # if this callback has been hooked for this event
2402 $CallbackData = array("Callback" => $EventCallback, "Order" => $Order);
2403 if (in_array($CallbackData,
2404 $this->RegisteredEvents[$EventName]["Hooks"]))
2405 {
2406 # unhook callback
2407 $HookIndex = array_search($CallbackData,
2408 $this->RegisteredEvents[$EventName]["Hooks"]);
2409 unset($this->RegisteredEvents[$EventName]["Hooks"][$HookIndex]);
2410 $UnhookCount++;
2411 }
2412 }
2413 }
2414
2415 # report number of callbacks unhooked to caller
2416 return $UnhookCount;
2417 }
2418
2429 public function SignalEvent($EventName, $Parameters = NULL)
2430 {
2431 $ReturnValue = NULL;
2432
2433 # if event has been registered
2434 if (isset($this->RegisteredEvents[$EventName]))
2435 {
2436 # set up default return value (if not NULL)
2437 switch ($this->RegisteredEvents[$EventName]["Type"])
2438 {
2439 case self::EVENTTYPE_CHAIN:
2440 $ReturnValue = $Parameters;
2441 break;
2442
2443 case self::EVENTTYPE_NAMED:
2444 $ReturnValue = array();
2445 break;
2446 }
2447
2448 # for each callback for this event
2449 foreach ($this->RegisteredEvents[$EventName]["Hooks"] as $Hook)
2450 {
2451 # invoke callback
2452 $Callback = $Hook["Callback"];
2453 $Result = ($Parameters !== NULL)
2454 ? call_user_func_array($Callback, $Parameters)
2455 : call_user_func($Callback);
2456
2457 # process return value based on event type
2458 switch ($this->RegisteredEvents[$EventName]["Type"])
2459 {
2460 case self::EVENTTYPE_CHAIN:
2461 if ($Result !== NULL)
2462 {
2463 foreach ($Parameters as $Index => $Value)
2464 {
2465 if (array_key_exists($Index, $Result))
2466 {
2467 $Parameters[$Index] = $Result[$Index];
2468 }
2469 }
2470 $ReturnValue = $Parameters;
2471 }
2472 break;
2473
2474 case self::EVENTTYPE_FIRST:
2475 if ($Result !== NULL)
2476 {
2477 $ReturnValue = $Result;
2478 break 2;
2479 }
2480 break;
2481
2482 case self::EVENTTYPE_NAMED:
2483 $CallbackName = is_array($Callback)
2484 ? (is_object($Callback[0])
2485 ? get_class($Callback[0])
2486 : $Callback[0])."::".$Callback[1]
2487 : $Callback;
2488 $ReturnValue[$CallbackName] = $Result;
2489 break;
2490
2491 default:
2492 break;
2493 }
2494 }
2495 }
2496 else
2497 {
2498 $this->LogError(self::LOGLVL_WARNING,
2499 "Unregistered event (".$EventName.") signaled by "
2500 .StdLib::GetMyCaller().".");
2501 }
2502
2503 # return value if any to caller
2504 return $ReturnValue;
2505 }
2506
2512 public function IsStaticOnlyEvent($EventName)
2513 {
2514 return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
2515 }
2516
2527 public function EventWillNextRunAt($EventName, $Callback)
2528 {
2529 # if event is not a periodic event report failure to caller
2530 if (!array_key_exists($EventName, $this->EventPeriods)) { return FALSE; }
2531
2532 # retrieve last execution time for event if available
2533 $Signature = self::GetCallbackSignature($Callback);
2534 $LastRunTime = $this->DB->Query("SELECT LastRunAt FROM PeriodicEvents"
2535 ." WHERE Signature = '".addslashes($Signature)."'", "LastRunAt");
2536
2537 # if event was not found report failure to caller
2538 if ($LastRunTime === NULL) { return FALSE; }
2539
2540 # calculate next run time based on event period
2541 $NextRunTime = strtotime($LastRunTime) + $this->EventPeriods[$EventName];
2542
2543 # report next run time to caller
2544 return $NextRunTime;
2545 }
2546
2562 public function GetKnownPeriodicEvents()
2563 {
2564 # retrieve last execution times
2565 $this->DB->Query("SELECT * FROM PeriodicEvents");
2566 $LastRunTimes = $this->DB->FetchColumn("LastRunAt", "Signature");
2567
2568 # for each known event
2569 $Events = array();
2570 foreach ($this->KnownPeriodicEvents as $Signature => $Info)
2571 {
2572 # if last run time for event is available
2573 if (array_key_exists($Signature, $LastRunTimes))
2574 {
2575 # calculate next run time for event
2576 $LastRun = strtotime($LastRunTimes[$Signature]);
2577 $NextRun = $LastRun + $this->EventPeriods[$Info["Period"]];
2578 if ($Info["Period"] == "EVENT_PERIODIC") { $LastRun = FALSE; }
2579 }
2580 else
2581 {
2582 # set info to indicate run times are not known
2583 $LastRun = FALSE;
2584 $NextRun = FALSE;
2585 }
2586
2587 # add event info to list
2588 $Events[$Signature] = $Info;
2589 $Events[$Signature]["LastRun"] = $LastRun;
2590 $Events[$Signature]["NextRun"] = $NextRun;
2591 $Events[$Signature]["Parameters"] = NULL;
2592 }
2593
2594 # return list of known events to caller
2595 return $Events;
2596 }
2597
2598 /*@)*/ /* Event Handling */
2599
2600
2601 # ---- Task Management ---------------------------------------------------
2602 /*@(*/
2604
2606 const PRIORITY_HIGH = 1;
2608 const PRIORITY_MEDIUM = 2;
2610 const PRIORITY_LOW = 3;
2612 const PRIORITY_BACKGROUND = 4;
2613
2626 public function QueueTask($Callback, $Parameters = NULL,
2627 $Priority = self::PRIORITY_LOW, $Description = "")
2628 {
2629 # pack task info and write to database
2630 if ($Parameters === NULL) { $Parameters = array(); }
2631 $this->DB->Query("INSERT INTO TaskQueue"
2632 ." (Callback, Parameters, Priority, Description)"
2633 ." VALUES ('".addslashes(serialize($Callback))."', '"
2634 .addslashes(serialize($Parameters))."', ".intval($Priority).", '"
2635 .addslashes($Description)."')");
2636 }
2637
2655 public function QueueUniqueTask($Callback, $Parameters = NULL,
2656 $Priority = self::PRIORITY_LOW, $Description = "")
2657 {
2658 if ($this->TaskIsInQueue($Callback, $Parameters))
2659 {
2660 $QueryResult = $this->DB->Query("SELECT TaskId,Priority FROM TaskQueue"
2661 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2662 .($Parameters ? " AND Parameters = '"
2663 .addslashes(serialize($Parameters))."'" : ""));
2664 if ($QueryResult !== FALSE)
2665 {
2666 $Record = $this->DB->FetchRow();
2667 if ($Record["Priority"] > $Priority)
2668 {
2669 $this->DB->Query("UPDATE TaskQueue"
2670 ." SET Priority = ".intval($Priority)
2671 ." WHERE TaskId = ".intval($Record["TaskId"]));
2672 }
2673 }
2674 return FALSE;
2675 }
2676 else
2677 {
2678 $this->QueueTask($Callback, $Parameters, $Priority, $Description);
2679 return TRUE;
2680 }
2681 }
2682
2692 public function TaskIsInQueue($Callback, $Parameters = NULL)
2693 {
2694 $QueuedCount = $this->DB->Query(
2695 "SELECT COUNT(*) AS FoundCount FROM TaskQueue"
2696 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2697 .($Parameters ? " AND Parameters = '"
2698 .addslashes(serialize($Parameters))."'" : ""),
2699 "FoundCount");
2700 $RunningCount = $this->DB->Query(
2701 "SELECT COUNT(*) AS FoundCount FROM RunningTasks"
2702 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2703 .($Parameters ? " AND Parameters = '"
2704 .addslashes(serialize($Parameters))."'" : ""),
2705 "FoundCount");
2706 $FoundCount = $QueuedCount + $RunningCount;
2707 return ($FoundCount ? TRUE : FALSE);
2708 }
2709
2715 public function GetTaskQueueSize($Priority = NULL)
2716 {
2717 return $this->GetQueuedTaskCount(NULL, NULL, $Priority);
2718 }
2719
2727 public function GetQueuedTaskList($Count = 100, $Offset = 0)
2728 {
2729 return $this->GetTaskList("SELECT * FROM TaskQueue"
2730 ." ORDER BY Priority, TaskId ", $Count, $Offset);
2731 }
2732
2746 public function GetQueuedTaskCount($Callback = NULL,
2747 $Parameters = NULL, $Priority = NULL, $Description = NULL)
2748 {
2749 $Query = "SELECT COUNT(*) AS TaskCount FROM TaskQueue";
2750 $Sep = " WHERE";
2751 if ($Callback !== NULL)
2752 {
2753 $Query .= $Sep." Callback = '".addslashes(serialize($Callback))."'";
2754 $Sep = " AND";
2755 }
2756 if ($Parameters !== NULL)
2757 {
2758 $Query .= $Sep." Parameters = '".addslashes(serialize($Parameters))."'";
2759 $Sep = " AND";
2760 }
2761 if ($Priority !== NULL)
2762 {
2763 $Query .= $Sep." Priority = ".intval($Priority);
2764 $Sep = " AND";
2765 }
2766 if ($Description !== NULL)
2767 {
2768 $Query .= $Sep." Description = '".addslashes($Description)."'";
2769 }
2770 return $this->DB->Query($Query, "TaskCount");
2771 }
2772
2780 public function GetRunningTaskList($Count = 100, $Offset = 0)
2781 {
2782 return $this->GetTaskList("SELECT * FROM RunningTasks"
2783 ." WHERE StartedAt >= '".date("Y-m-d H:i:s",
2784 (time() - $this->MaxExecutionTime()))."'"
2785 ." ORDER BY StartedAt", $Count, $Offset);
2786 }
2787
2795 public function GetOrphanedTaskList($Count = 100, $Offset = 0)
2796 {
2797 return $this->GetTaskList("SELECT * FROM RunningTasks"
2798 ." WHERE StartedAt < '".date("Y-m-d H:i:s",
2799 (time() - $this->MaxExecutionTime()))."'"
2800 ." ORDER BY StartedAt", $Count, $Offset);
2801 }
2802
2807 public function GetOrphanedTaskCount()
2808 {
2809 return $this->DB->Query("SELECT COUNT(*) AS Count FROM RunningTasks"
2810 ." WHERE StartedAt < '".date("Y-m-d H:i:s",
2811 (time() - $this->MaxExecutionTime()))."'",
2812 "Count");
2813 }
2814
2820 public function ReQueueOrphanedTask($TaskId, $NewPriority = NULL)
2821 {
2822 $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
2823 $this->DB->Query("INSERT INTO TaskQueue"
2824 ." (Callback,Parameters,Priority,Description) "
2825 ."SELECT Callback, Parameters, Priority, Description"
2826 ." FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2827 if ($NewPriority !== NULL)
2828 {
2829 $NewTaskId = $this->DB->LastInsertId();
2830 $this->DB->Query("UPDATE TaskQueue SET Priority = "
2831 .intval($NewPriority)
2832 ." WHERE TaskId = ".intval($NewTaskId));
2833 }
2834 $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2835 $this->DB->Query("UNLOCK TABLES");
2836 }
2837
2842 public function DeleteTask($TaskId)
2843 {
2844 $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2845 $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2846 }
2847
2855 public function GetTask($TaskId)
2856 {
2857 # assume task will not be found
2858 $Task = NULL;
2859
2860 # look for task in task queue
2861 $this->DB->Query("SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2862
2863 # if task was not found in queue
2864 if (!$this->DB->NumRowsSelected())
2865 {
2866 # look for task in running task list
2867 $this->DB->Query("SELECT * FROM RunningTasks WHERE TaskId = "
2868 .intval($TaskId));
2869 }
2870
2871 # if task was found
2872 if ($this->DB->NumRowsSelected())
2873 {
2874 # if task was periodic
2875 $Row = $this->DB->FetchRow();
2876 if ($Row["Callback"] ==
2877 serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
2878 {
2879 # unpack periodic task callback
2880 $WrappedCallback = unserialize($Row["Parameters"]);
2881 $Task["Callback"] = $WrappedCallback[1];
2882 $Task["Parameters"] = $WrappedCallback[2];
2883 }
2884 else
2885 {
2886 # unpack task callback and parameters
2887 $Task["Callback"] = unserialize($Row["Callback"]);
2888 $Task["Parameters"] = unserialize($Row["Parameters"]);
2889 }
2890 }
2891
2892 # return task to caller
2893 return $Task;
2894 }
2895
2903 public function TaskExecutionEnabled($NewValue = DB_NOVALUE)
2904 {
2905 return $this->UpdateSetting("TaskExecutionEnabled", $NewValue);
2906 }
2907
2913 public function MaxTasks($NewValue = DB_NOVALUE)
2914 {
2915 return $this->UpdateSetting("MaxTasksRunning", $NewValue);
2916 }
2917
2925 public static function GetTaskCallbackSynopsis($TaskInfo)
2926 {
2927 # if task callback is function use function name
2928 $Callback = $TaskInfo["Callback"];
2929 $Name = "";
2930 if (!is_array($Callback))
2931 {
2932 $Name = $Callback;
2933 }
2934 else
2935 {
2936 # if task callback is object
2937 if (is_object($Callback[0]))
2938 {
2939 # if task callback is encapsulated ask encapsulation for name
2940 if (method_exists($Callback[0], "GetCallbackAsText"))
2941 {
2942 $Name = $Callback[0]->GetCallbackAsText();
2943 }
2944 # else assemble name from object
2945 else
2946 {
2947 $Name = get_class($Callback[0]) . "::" . $Callback[1];
2948 }
2949 }
2950 # else assemble name from supplied info
2951 else
2952 {
2953 $Name= $Callback[0] . "::" . $Callback[1];
2954 }
2955 }
2956
2957 # if parameter array was supplied
2958 $Parameters = $TaskInfo["Parameters"];
2959 $ParameterString = "";
2960 if (is_array($Parameters))
2961 {
2962 # assemble parameter string
2963 $Separator = "";
2964 foreach ($Parameters as $Parameter)
2965 {
2966 $ParameterString .= $Separator;
2967 if (is_int($Parameter) || is_float($Parameter))
2968 {
2969 $ParameterString .= $Parameter;
2970 }
2971 else if (is_string($Parameter))
2972 {
2973 $ParameterString .= "\"".htmlspecialchars($Parameter)."\"";
2974 }
2975 else if (is_array($Parameter))
2976 {
2977 $ParameterString .= "ARRAY";
2978 }
2979 else if (is_object($Parameter))
2980 {
2981 $ParameterString .= "OBJECT";
2982 }
2983 else if (is_null($Parameter))
2984 {
2985 $ParameterString .= "NULL";
2986 }
2987 else if (is_bool($Parameter))
2988 {
2989 $ParameterString .= $Parameter ? "TRUE" : "FALSE";
2990 }
2991 else if (is_resource($Parameter))
2992 {
2993 $ParameterString .= get_resource_type($Parameter);
2994 }
2995 else
2996 {
2997 $ParameterString .= "????";
2998 }
2999 $Separator = ", ";
3000 }
3001 }
3002
3003 # assemble name and parameters and return result to caller
3004 return $Name."(".$ParameterString.")";
3005 }
3006
3011 public function IsRunningInBackground()
3012 {
3013 return $this->RunningInBackground;
3014 }
3015
3021 public function GetCurrentBackgroundPriority()
3022 {
3023 return isset($this->RunningTask)
3024 ? $this->RunningTask["Priority"] : NULL;
3025 }
3026
3035 public function GetNextHigherBackgroundPriority($Priority = NULL)
3036 {
3037 if ($Priority === NULL)
3038 {
3039 $Priority = $this->GetCurrentBackgroundPriority();
3040 if ($Priority === NULL)
3041 {
3042 return NULL;
3043 }
3044 }
3045 return ($Priority > self::PRIORITY_HIGH)
3046 ? ($Priority - 1) : self::PRIORITY_HIGH;
3047 }
3048
3057 public function GetNextLowerBackgroundPriority($Priority = NULL)
3058 {
3059 if ($Priority === NULL)
3060 {
3061 $Priority = $this->GetCurrentBackgroundPriority();
3062 if ($Priority === NULL)
3063 {
3064 return NULL;
3065 }
3066 }
3067 return ($Priority < self::PRIORITY_BACKGROUND)
3068 ? ($Priority + 1) : self::PRIORITY_BACKGROUND;
3069 }
3070
3071 /*@)*/ /* Task Management */
3072
3073
3074 # ---- Clean URL Support -------------------------------------------------
3075 /*@(*/
3077
3104 public function AddCleanUrl($Pattern, $Page, $GetVars = NULL, $Template = NULL)
3105 {
3106 # save clean URL mapping parameters
3107 $this->CleanUrlMappings[] = array(
3108 "Pattern" => $Pattern,
3109 "Page" => $Page,
3110 "GetVars" => $GetVars,
3111 );
3112
3113 # if replacement template specified
3114 if ($Template !== NULL)
3115 {
3116 # if GET parameters specified
3117 if (count($GetVars))
3118 {
3119 # retrieve all possible permutations of GET parameters
3120 $GetPerms = $this->ArrayPermutations(array_keys($GetVars));
3121
3122 # for each permutation of GET parameters
3123 foreach ($GetPerms as $VarPermutation)
3124 {
3125 # construct search pattern for permutation
3126 $SearchPattern = "/href=([\"'])index\\.php\\?P=".$Page;
3127 $GetVarSegment = "";
3128 foreach ($VarPermutation as $GetVar)
3129 {
3130 if (preg_match("%\\\$[0-9]+%", $GetVars[$GetVar]))
3131 {
3132 $GetVarSegment .= "&amp;".$GetVar."=((?:(?!\\1)[^&])+)";
3133 }
3134 else
3135 {
3136 $GetVarSegment .= "&amp;".$GetVar."=".$GetVars[$GetVar];
3137 }
3138 }
3139 $SearchPattern .= $GetVarSegment."\\1/i";
3140
3141 # if template is actually a callback
3142 if (is_callable($Template))
3143 {
3144 # add pattern to HTML output mod callbacks list
3145 $this->OutputModificationCallbacks[] = array(
3146 "Pattern" => $Pattern,
3147 "Page" => $Page,
3148 "SearchPattern" => $SearchPattern,
3149 "Callback" => $Template,
3150 );
3151 }
3152 else
3153 {
3154 # construct replacement string for permutation
3155 $Replacement = $Template;
3156 $Index = 2;
3157 foreach ($VarPermutation as $GetVar)
3158 {
3159 $Replacement = str_replace(
3160 "\$".$GetVar, "\$".$Index, $Replacement);
3161 $Index++;
3162 }
3163 $Replacement = "href=\"".$Replacement."\"";
3164
3165 # add pattern to HTML output modifications list
3166 $this->OutputModificationPatterns[] = $SearchPattern;
3167 $this->OutputModificationReplacements[] = $Replacement;
3168 }
3169 }
3170 }
3171 else
3172 {
3173 # construct search pattern
3174 $SearchPattern = "/href=\"index\\.php\\?P=".$Page."\"/i";
3175
3176 # if template is actually a callback
3177 if (is_callable($Template))
3178 {
3179 # add pattern to HTML output mod callbacks list
3180 $this->OutputModificationCallbacks[] = array(
3181 "Pattern" => $Pattern,
3182 "Page" => $Page,
3183 "SearchPattern" => $SearchPattern,
3184 "Callback" => $Template,
3185 );
3186 }
3187 else
3188 {
3189 # add simple pattern to HTML output modifications list
3190 $this->OutputModificationPatterns[] = $SearchPattern;
3191 $this->OutputModificationReplacements[] = "href=\"".$Template."\"";
3192 }
3193 }
3194 }
3195 }
3196
3202 public function CleanUrlIsMapped($Path)
3203 {
3204 foreach ($this->CleanUrlMappings as $Info)
3205 {
3206 if (preg_match($Info["Pattern"], $Path))
3207 {
3208 return TRUE;
3209 }
3210 }
3211 return FALSE;
3212 }
3213
3223 public function GetCleanUrlForPath($Path)
3224 {
3225 # the search patterns and callbacks require a specific format
3226 $Format = "href=\"".str_replace("&", "&amp;", $Path)."\"";
3227 $Search = $Format;
3228
3229 # perform any regular expression replacements on the search string
3230 $Search = preg_replace($this->OutputModificationPatterns,
3231 $this->OutputModificationReplacements, $Search);
3232
3233 # only run the callbacks if a replacement hasn't already been performed
3234 if ($Search == $Format)
3235 {
3236 # perform any callback replacements on the search string
3237 foreach ($this->OutputModificationCallbacks as $Info)
3238 {
3239 # make the information available to the callback
3240 $this->OutputModificationCallbackInfo = $Info;
3241
3242 # execute the callback
3243 $Search = preg_replace_callback($Info["SearchPattern"],
3244 array($this, "OutputModificationCallbackShell"),
3245 $Search);
3246 }
3247 }
3248
3249 # return the path untouched if no replacements were performed
3250 if ($Search == $Format)
3251 {
3252 return $Path;
3253 }
3254
3255 # remove the bits added to the search string to get it recognized by
3256 # the replacement expressions and callbacks
3257 $Result = substr($Search, 6, -1);
3258
3259 return $Result;
3260 }
3261
3268 public function GetUncleanUrlForPath($Path)
3269 {
3270 # for each clean URL mapping
3271 foreach ($this->CleanUrlMappings as $Info)
3272 {
3273 # if current path matches the clean URL pattern
3274 if (preg_match($Info["Pattern"], $Path, $Matches))
3275 {
3276 # the GET parameters for the URL, starting with the page name
3277 $GetVars = array("P" => $Info["Page"]);
3278
3279 # if additional $_GET variables specified for clean URL
3280 if ($Info["GetVars"] !== NULL)
3281 {
3282 # for each $_GET variable specified for clean URL
3283 foreach ($Info["GetVars"] as $VarName => $VarTemplate)
3284 {
3285 # start with template for variable value
3286 $Value = $VarTemplate;
3287
3288 # for each subpattern matched in current URL
3289 foreach ($Matches as $Index => $Match)
3290 {
3291 # if not first (whole) match
3292 if ($Index > 0)
3293 {
3294 # make any substitutions in template
3295 $Value = str_replace("$".$Index, $Match, $Value);
3296 }
3297 }
3298
3299 # add the GET variable
3300 $GetVars[$VarName] = $Value;
3301 }
3302 }
3303
3304 # return the unclean URL
3305 return "index.php?" . http_build_query($GetVars);
3306 }
3307 }
3308
3309 # return the path unchanged
3310 return $Path;
3311 }
3312
3318 public function GetCleanUrl()
3319 {
3320 return $this->GetCleanUrlForPath($this->GetUncleanUrl());
3321 }
3322
3327 public function GetUncleanUrl()
3328 {
3329 $GetVars = array("P" => $this->GetPageName()) + $_GET;
3330 return "index.php?" . http_build_query($GetVars);
3331 }
3332
3344 public function AddPrefixForAlternateDomain($Domain, $Prefix)
3345 {
3346 $this->AlternateDomainPrefixes[$Domain] = $Prefix;
3347 }
3348
3349 /*@)*/ /* Clean URL Support */
3350
3351 # ---- Server Environment ------------------------------------------------
3352 /*@(*/
3354
3360 public static function SessionLifetime($NewValue = NULL)
3361 {
3362 if ($NewValue !== NULL)
3363 {
3364 self::$SessionLifetime = $NewValue;
3365 }
3366 return self::$SessionLifetime;
3367 }
3368
3374 public static function HtaccessSupport()
3375 {
3376 return isset($_SERVER["HTACCESS_SUPPORT"])
3377 || isset($_SERVER["REDIRECT_HTACCESS_SUPPORT"]);
3378 }
3379
3386 public static function UrlFingerprintingRewriteSupport()
3387 {
3388 return isset($_SERVER["URL_FINGERPRINTING_SUPPORT"])
3389 || isset($_SERVER["REDIRECT_URL_FINGERPRINTING_SUPPORT"]);
3390 }
3391
3398 public static function ScssRewriteSupport()
3399 {
3400 return isset($_SERVER["SCSS_REWRITE_SUPPORT"])
3401 || isset($_SERVER["REDIRECT_SCSS_REWRITE_SUPPORT"]);
3402 }
3403
3410 public static function JsMinRewriteSupport()
3411 {
3412 return isset($_SERVER["JSMIN_REWRITE_SUPPORT"])
3413 || isset($_SERVER["REDIRECT_JSMIN_REWRITE_SUPPORT"]);
3414 }
3415
3423 public static function RootUrl()
3424 {
3425 # return override value if one is set
3426 if (self::$RootUrlOverride !== NULL)
3427 {
3428 return self::$RootUrlOverride;
3429 }
3430
3431 # determine scheme name
3432 $Protocol = (isset($_SERVER["HTTPS"]) ? "https" : "http");
3433
3434 # if HTTP_HOST is preferred or SERVER_NAME points to localhost
3435 # and HTTP_HOST is set
3436 if ((self::$PreferHttpHost || ($_SERVER["SERVER_NAME"] == "127.0.0.1"))
3437 && isset($_SERVER["HTTP_HOST"]))
3438 {
3439 # use HTTP_HOST for domain name
3440 $DomainName = $_SERVER["HTTP_HOST"];
3441 }
3442 else
3443 {
3444 # use SERVER_NAME for domain name
3445 $DomainName = $_SERVER["HTTP_HOST"];
3446 }
3447
3448 # build URL root and return to caller
3449 return $Protocol."://".$DomainName;
3450 }
3451
3466 public static function RootUrlOverride($NewValue = self::NOVALUE)
3467 {
3468 if ($NewValue !== self::NOVALUE)
3469 {
3470 self::$RootUrlOverride = strlen(trim($NewValue)) ? $NewValue : NULL;
3471 }
3472 return self::$RootUrlOverride;
3473 }
3474
3484 public static function BaseUrl()
3485 {
3486 $BaseUrl = self::RootUrl().dirname($_SERVER["SCRIPT_NAME"]);
3487 if (substr($BaseUrl, -1) != "/") { $BaseUrl .= "/"; }
3488 return $BaseUrl;
3489 }
3490
3498 public static function FullUrl()
3499 {
3500 return self::RootUrl().$_SERVER["REQUEST_URI"];
3501 }
3502
3513 public static function PreferHttpHost($NewValue = NULL)
3514 {
3515 if ($NewValue !== NULL)
3516 {
3517 self::$PreferHttpHost = ($NewValue ? TRUE : FALSE);
3518 }
3519 return self::$PreferHttpHost;
3520 }
3521
3526 public static function BasePath()
3527 {
3528 $BasePath = dirname($_SERVER["SCRIPT_NAME"]);
3529
3530 if (substr($BasePath, -1) != "/")
3531 {
3532 $BasePath .= "/";
3533 }
3534
3535 return $BasePath;
3536 }
3537
3543 public static function GetScriptUrl()
3544 {
3545 if (array_key_exists("SCRIPT_URL", $_SERVER))
3546 {
3547 return $_SERVER["SCRIPT_URL"];
3548 }
3549 elseif (array_key_exists("REQUEST_URI", $_SERVER))
3550 {
3551 $Pieces = parse_url($_SERVER["REQUEST_URI"]);
3552 return isset($Pieces["path"]) ? $Pieces["path"] : NULL;
3553 }
3554 elseif (array_key_exists("REDIRECT_URL", $_SERVER))
3555 {
3556 return $_SERVER["REDIRECT_URL"];
3557 }
3558 else
3559 {
3560 return NULL;
3561 }
3562 }
3563
3572 public static function WasUrlRewritten($ScriptName="index.php")
3573 {
3574 # needed to get the path of the URL minus the query and fragment pieces
3575 $Components = parse_url(self::GetScriptUrl());
3576
3577 # if parsing was successful and a path is set
3578 if (is_array($Components) && isset($Components["path"]))
3579 {
3580 $BasePath = self::BasePath();
3581 $Path = $Components["path"];
3582
3583 # the URL was rewritten if the path isn't the base path, i.e., the
3584 # home page, and the file in the URL isn't the script generating the
3585 # page
3586 if ($BasePath != $Path && basename($Path) != $ScriptName)
3587 {
3588 return TRUE;
3589 }
3590 }
3591
3592 # the URL wasn't rewritten
3593 return FALSE;
3594 }
3595
3601 public static function GetFreeMemory()
3602 {
3603 return self::GetPhpMemoryLimit() - memory_get_usage();
3604 }
3605
3611 public static function GetPhpMemoryLimit()
3612 {
3613 $Str = strtoupper(ini_get("memory_limit"));
3614 if (substr($Str, -1) == "B") { $Str = substr($Str, 0, strlen($Str) - 1); }
3615 switch (substr($Str, -1))
3616 {
3617 case "K":
3618 $MemoryLimit = (int)$Str * 1024;
3619 break;
3620
3621 case "M":
3622 $MemoryLimit = (int)$Str * 1048576;
3623 break;
3624
3625 case "G":
3626 $MemoryLimit = (int)$Str * 1073741824;
3627 break;
3628
3629 default:
3630 $MemoryLimit = (int)$Str;
3631 break;
3632 }
3633 return $MemoryLimit;
3634 }
3635
3648 public function MaxExecutionTime($NewValue = DB_NOVALUE, $Persistent = FALSE)
3649 {
3650 if ($NewValue !== DB_NOVALUE)
3651 {
3652 $NewValue = max($NewValue, 5);
3653 ini_set("max_execution_time", $NewValue);
3654 set_time_limit($NewValue - $this->GetElapsedExecutionTime());
3655 $this->UpdateSetting("MaxExecTime", $NewValue, $Persistent);
3656 }
3657 return ini_get("max_execution_time");
3658 }
3659
3660 /*@)*/ /* Server Environment */
3661
3662
3663 # ---- Utility -----------------------------------------------------------
3664 /*@(*/
3666
3678 public function DownloadFile($FilePath, $FileName = NULL, $MimeType = NULL)
3679 {
3680 # check that file is readable
3681 if (!is_readable($FilePath))
3682 {
3683 return FALSE;
3684 }
3685
3686 # if file name was not supplied
3687 if ($FileName === NULL)
3688 {
3689 # extract file name from path
3690 $FileName = basename($FilePath);
3691 }
3692
3693 # if MIME type was not supplied
3694 if ($MimeType === NULL)
3695 {
3696 # attempt to determine MIME type
3697 $FInfoHandle = finfo_open(FILEINFO_MIME);
3698 if ($FInfoHandle)
3699 {
3700 $FInfoMime = finfo_file($FInfoHandle, $FilePath);
3701 finfo_close($FInfoHandle);
3702 if ($FInfoMime)
3703 {
3704 $MimeType = $FInfoMime;
3705 }
3706 }
3707
3708 # use default if unable to determine MIME type
3709 if ($MimeType === NULL)
3710 {
3711 $MimeType = "application/octet-stream";
3712 }
3713 }
3714
3715 # set headers to download file
3716 header("Content-Type: ".$MimeType);
3717 header("Content-Length: ".filesize($FilePath));
3718 if ($this->CleanUrlRewritePerformed)
3719 {
3720 header('Content-Disposition: attachment; filename="'.$FileName.'"');
3721 }
3722
3723 # make sure that apache does not attempt to compress file
3724 apache_setenv('no-gzip', '1');
3725
3726 # send file to user, but unbuffered to avoid memory issues
3727 $this->AddUnbufferedCallback(function ($File)
3728 {
3729 $BlockSize = 512000;
3730 $Handle = @fopen($File, "rb");
3731 if ($Handle === FALSE)
3732 {
3733 return;
3734 }
3735 while (!feof($Handle))
3736 {
3737 print fread($Handle, $BlockSize);
3738 flush();
3739 }
3740 fclose($Handle);
3741 }, array($FilePath));
3742
3743 # prevent HTML output that might interfere with download
3744 $this->SuppressHTMLOutput();
3745
3746 # set flag to indicate not to log a slow page load in case client
3747 # connection delays PHP execution because of header
3748 $this->DoNotLogSlowPageLoad = TRUE;
3749
3750 # report no errors found to caller
3751 return TRUE;
3752 }
3753
3766 public function GetLock($LockName, $Wait = TRUE)
3767 {
3768 # assume we will not get a lock
3769 $GotLock = FALSE;
3770
3771 # clear out any stale locks
3772 static $CleanupHasBeenDone = FALSE;
3773 if (!$CleanupHasBeenDone)
3774 {
3775 # (margin for clearing stale locks is twice the known
3776 # maximum PHP execution time, because the max time
3777 # techinically does not include external operations
3778 # like database queries)
3779 $ClearLocksObtainedBefore = date(StdLib::SQL_DATE_FORMAT,
3780 (time() - ($this->MaxExecutionTime() * 2)));
3781 $this->DB->Query("DELETE FROM AF_Locks WHERE"
3782 ." ObtainedAt < '".$ClearLocksObtainedBefore."' AND"
3783 ." LockName = '".addslashes($LockName)."'");
3784 }
3785
3786 do
3787 {
3788 # lock database table so nobody else can try to get a lock
3789 $this->DB->Query("LOCK TABLES AF_Locks WRITE");
3790
3791 # look for lock with specified name
3792 $FoundCount = $this->DB->Query("SELECT COUNT(*) AS FoundCount"
3793 ." FROM AF_Locks WHERE LockName = '"
3794 .addslashes($LockName)."'", "FoundCount");
3795 $LockFound = ($FoundCount > 0) ? TRUE : FALSE;
3796
3797 # if lock found
3798 if ($LockFound)
3799 {
3800 # unlock database tables
3801 $this->DB->Query("UNLOCK TABLES");
3802
3803 # if blocking was requested
3804 if ($Wait)
3805 {
3806 # wait to give someone else a chance to release lock
3807 sleep(2);
3808 }
3809 }
3810 // @codingStandardsIgnoreStart
3811 // (because phpcs does not correctly handle do-while loops)
3812 # while lock was found and blocking was requested
3813 } while ($LockFound && $Wait);
3814 // @codingStandardsIgnoreEnd
3815
3816 # if lock was not found
3817 if (!$LockFound)
3818 {
3819 # get our lock
3820 $this->DB->Query("INSERT INTO AF_Locks (LockName) VALUES ('"
3821 .addslashes($LockName)."')");
3822 $GotLock = TRUE;
3823
3824 # unlock database tables
3825 $this->DB->Query("UNLOCK TABLES");
3826 }
3827
3828 # report to caller whether lock was obtained
3829 return $GotLock;
3830 }
3831
3839 public function ReleaseLock($LockName)
3840 {
3841 # release any existing locks
3842 $this->DB->Query("DELETE FROM AF_Locks WHERE LockName = '"
3843 .addslashes($LockName)."'");
3844
3845 # report to caller whether existing lock was released
3846 return $this->DB->NumRowsAffected() ? TRUE : FALSE;
3847 }
3848
3849 /*@)*/ /* Utility */
3850
3851
3852 # ---- Backward Compatibility --------------------------------------------
3853 /*@(*/
3855
3862 public function FindCommonTemplate($BaseName)
3863 {
3864 return $this->FindFile(
3865 $this->IncludeDirList, $BaseName, array("tpl", "html"));
3866 }
3867
3868 /*@)*/ /* Backward Compatibility */
3869
3870
3871 # ---- PRIVATE INTERFACE -------------------------------------------------
3872
3873 private $AdditionalRequiredUIFiles = array();
3874 private $BackgroundTaskMemLeakLogThreshold = 10; # percentage of max mem
3875 private $BackgroundTaskMinFreeMemPercent = 25;
3876 private $BrowserDetectFunc;
3877 private $CacheCurrentPage = TRUE;
3878 private $AlternateDomainPrefixes = array();
3879 private $CleanUrlMappings = array();
3880 private $CleanUrlRewritePerformed = FALSE;
3881 private $CssUrlFingerprintPath;
3882 private $DB;
3883 private $DefaultPage = "Home";
3884 private $DoNotMinimizeList = array();
3885 private $DoNotLogSlowPageLoad = FALSE;
3886 private $EnvIncludes = array();
3887 private $ExecutionStartTime;
3888 private $FoundUIFiles = array();
3889 private $HtmlCharset = "UTF-8";
3890 private $JSMinimizerJavaScriptPackerAvailable = FALSE;
3891 private $JSMinimizerJShrinkAvailable = TRUE;
3892 private $JumpToPage = NULL;
3893 private $JumpToPageDelay = 0;
3894 private $LogFileName = "local/logs/site.log";
3895 private $MaxRunningTasksToTrack = 250;
3896 private $OutputModificationCallbackInfo;
3897 private $OutputModificationCallbacks = array();
3898 private $OutputModificationPatterns = array();
3899 private $OutputModificationReplacements = array();
3900 private $PageCacheTags = array();
3901 private $PageName;
3902 private $PostProcessingFuncs = array();
3903 private $RunningInBackground = FALSE;
3904 private $RunningTask;
3905 private $SavedContext;
3906 private $SaveTemplateLocationCache = FALSE;
3907 private $SessionStorage;
3908 private $SessionGcProbability;
3909 private $Settings;
3910 private $SuppressHTML = FALSE;
3911 private $TemplateLocationCache;
3912 private $TemplateLocationCacheInterval = 60; # in minutes
3913 private $TemplateLocationCacheExpiration;
3914 private $UnbufferedCallbacks = array();
3915 private $UrlFingerprintBlacklist = array();
3916 private $UseBaseTag = FALSE;
3917
3918 private static $ActiveUI = "default";
3919 private static $AppName = "ScoutAF";
3920 private static $DefaultUI = "default";
3921 private static $JSMinCacheDir = "local/data/caches/JSMin";
3922 private static $ObjectDirectories = array();
3923 private static $ObjectLocationCache;
3924 private static $ObjectLocationCacheInterval = 60;
3925 private static $ObjectLocationCacheExpiration;
3926 private static $PreferHttpHost = FALSE;
3927 private static $RootUrlOverride = NULL;
3928 private static $SaveObjectLocationCache = FALSE;
3929 private static $ScssCacheDir = "local/data/caches/SCSS";
3930 private static $SessionLifetime = 1440; # in seconds
3931
3932 # offset used to generate page cache tag IDs from numeric tags
3933 const PAGECACHETAGIDOFFSET = 100000;
3934
3935 # minimum expired session garbage collection probability
3936 const MIN_GC_PROBABILITY = 0.01;
3937
3942 private $NoTSR = FALSE;
3943
3944 private $KnownPeriodicEvents = array();
3945 private $PeriodicEvents = array(
3946 "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
3947 "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
3948 "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
3949 "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
3950 "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
3951 );
3952 private $EventPeriods = array(
3953 "EVENT_HOURLY" => 3600,
3954 "EVENT_DAILY" => 86400,
3955 "EVENT_WEEKLY" => 604800,
3956 "EVENT_MONTHLY" => 2592000,
3957 "EVENT_PERIODIC" => 0,
3958 );
3959 private $UIEvents = array(
3960 "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
3961 "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
3962 "EVENT_PHP_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
3963 "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
3964 "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
3965 "EVENT_PAGE_OUTPUT_FILTER" => self::EVENTTYPE_CHAIN,
3966 );
3967
3972 private function LoadSettings()
3973 {
3974 # read settings in from database
3975 $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
3976 $this->Settings = $this->DB->FetchRow();
3977
3978 # if settings were not previously initialized
3979 if ($this->Settings === FALSE)
3980 {
3981 # initialize settings in database
3982 $this->DB->Query("INSERT INTO ApplicationFrameworkSettings"
3983 ." (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
3984
3985 # read new settings in from database
3986 $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
3987 $this->Settings = $this->DB->FetchRow();
3988
3989 # bail out if reloading new settings failed
3990 if ($this->Settings === FALSE)
3991 {
3992 throw new Exception(
3993 "Unable to load application framework settings.");
3994 }
3995 }
3996
3997 # if base path was not previously set or we appear to have moved
3998 if (!array_key_exists("BasePath", $this->Settings)
3999 || (!strlen($this->Settings["BasePath"]))
4000 || (!array_key_exists("BasePathCheck", $this->Settings))
4001 || (__FILE__ != $this->Settings["BasePathCheck"]))
4002 {
4003 # attempt to extract base path from Apache .htaccess file
4004 if (is_readable(".htaccess"))
4005 {
4006 $Lines = file(".htaccess");
4007 foreach ($Lines as $Line)
4008 {
4009 if (preg_match("/\\s*RewriteBase\\s+/", $Line))
4010 {
4011 $Pieces = preg_split(
4012 "/\\s+/", $Line, NULL, PREG_SPLIT_NO_EMPTY);
4013 $BasePath = $Pieces[1];
4014 }
4015 }
4016 }
4017
4018 # if base path was found
4019 if (isset($BasePath))
4020 {
4021 # save base path locally
4022 $this->Settings["BasePath"] = $BasePath;
4023
4024 # save base path to database
4025 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
4026 ." SET BasePath = '".addslashes($BasePath)."'"
4027 .", BasePathCheck = '".addslashes(__FILE__)."'");
4028 }
4029 }
4030
4031 # retrieve template location cache
4032 $this->TemplateLocationCache = unserialize(
4033 $this->Settings["TemplateLocationCache"]);
4034 $this->TemplateLocationCacheInterval =
4035 $this->Settings["TemplateLocationCacheInterval"];
4036 $this->TemplateLocationCacheExpiration =
4037 strtotime($this->Settings["TemplateLocationCacheExpiration"]);
4038
4039 # if template location cache looks invalid or has expired
4040 $CurrentTime = time();
4041 if (!count($this->TemplateLocationCache)
4042 || ($CurrentTime >= $this->TemplateLocationCacheExpiration))
4043 {
4044 # clear cache and reset cache expiration
4045 $this->TemplateLocationCache = array();
4046 $this->TemplateLocationCacheExpiration =
4047 $CurrentTime + ($this->TemplateLocationCacheInterval * 60);
4048 $this->SaveTemplateLocationCache = TRUE;
4049 }
4050
4051 # retrieve object location cache
4052 self::$ObjectLocationCache =
4053 unserialize($this->Settings["ObjectLocationCache"]);
4054 self::$ObjectLocationCacheInterval =
4055 $this->Settings["ObjectLocationCacheInterval"];
4056 self::$ObjectLocationCacheExpiration =
4057 strtotime($this->Settings["ObjectLocationCacheExpiration"]);
4058
4059 # if object location cache looks invalid or has expired
4060 if (!count(self::$ObjectLocationCache)
4061 || ($CurrentTime >= self::$ObjectLocationCacheExpiration))
4062 {
4063 # clear cache and reset cache expiration
4064 self::$ObjectLocationCache = array();
4065 self::$ObjectLocationCacheExpiration =
4066 $CurrentTime + (self::$ObjectLocationCacheInterval * 60);
4067 self::$SaveObjectLocationCache = TRUE;
4068 }
4069 }
4070
4077 private function RewriteCleanUrls($PageName)
4078 {
4079 # if URL rewriting is supported by the server
4080 if ($this->HtaccessSupport())
4081 {
4082 # retrieve current URL and remove base path if present
4083 $Url = $this->GetPageLocation();
4084
4085 # for each clean URL mapping
4086 foreach ($this->CleanUrlMappings as $Info)
4087 {
4088 # if current URL matches clean URL pattern
4089 if (preg_match($Info["Pattern"], $Url, $Matches))
4090 {
4091 # set new page
4092 $PageName = $Info["Page"];
4093
4094 # if $_GET variables specified for clean URL
4095 if ($Info["GetVars"] !== NULL)
4096 {
4097 # for each $_GET variable specified for clean URL
4098 foreach ($Info["GetVars"] as $VarName => $VarTemplate)
4099 {
4100 # start with template for variable value
4101 $Value = $VarTemplate;
4102
4103 # for each subpattern matched in current URL
4104 foreach ($Matches as $Index => $Match)
4105 {
4106 # if not first (whole) match
4107 if ($Index > 0)
4108 {
4109 # make any substitutions in template
4110 $Value = str_replace("$".$Index, $Match, $Value);
4111 }
4112 }
4113
4114 # set $_GET variable
4115 $_GET[$VarName] = $Value;
4116 }
4117 }
4118
4119 # set flag indicating clean URL mapped
4120 $this->CleanUrlRewritePerformed = TRUE;
4121
4122 # stop looking for a mapping
4123 break;
4124 }
4125 }
4126 }
4127
4128 # return (possibly) updated page name to caller
4129 return $PageName;
4130 }
4131
4144 private function RewriteAlternateDomainUrls($Html)
4145 {
4146 if ($this->HtaccessSupport() &&
4147 self::$RootUrlOverride !== NULL)
4148 {
4149 $VHost = $_SERVER["SERVER_NAME"];
4150 if (isset($this->AlternateDomainPrefixes[$VHost]))
4151 {
4152 $ThisPrefix = $this->AlternateDomainPrefixes[$VHost];
4153
4154 # get the URL for the primary domain
4155 $RootUrl = $this->RootUrl()."/";
4156
4157 # and figure out what protcol we were using
4158 $Protocol = (isset($_SERVER["HTTPS"]) ? "https" : "http");
4159
4160 # convert all relative URLs absolute URLs within our
4161 # primary domain, then substitute in the alternate domain
4162 # for paths inside our configured prefix
4163 $RelativePathPatterns = array(
4164 "%src=\"(?!http://|https://)%i",
4165 "%src='(?!http://|https://)%i",
4166 "%href=\"(?!http://|https://)%i",
4167 "%href='(?!http://|https://)%i",
4168 "%action=\"(?!http://|https://)%i",
4169 "%action='(?!http://|https://)%i",
4170 "%@import\s+url\(\"(?!http://|https://)%i",
4171 "%@import\s+url\('(?!http://|https://)%i",
4172 "%src:\s+url\(\"(?!http://|https://)%i",
4173 "%src:\s+url\('(?!http://|https://)%i",
4174 "%@import\s+\"(?!http://|https://)%i",
4175 "%@import\s+'(?!http://|https://)%i",
4176 "%".preg_quote($RootUrl.$ThisPrefix."/", "%")."%",
4177 );
4178 $RelativePathReplacements = array(
4179 "src=\"".$RootUrl,
4180 "src='".$RootUrl,
4181 "href=\"".$RootUrl,
4182 "href='".$RootUrl,
4183 "action=\"".$RootUrl,
4184 "action='".$RootUrl,
4185 "@import url(\"".$RootUrl,
4186 "@import url('".$RootUrl,
4187 "src: url(\"".$RootUrl,
4188 "src: url('".$RootUrl,
4189 "@import \"".$RootUrl,
4190 "@import '".$RootUrl,
4191 $Protocol."://".$VHost."/",
4192 );
4193
4194 $NewHtml = preg_replace(
4195 $RelativePathPatterns,
4196 $RelativePathReplacements,
4197 $Html);
4198
4199 # check to make sure relative path fixes didn't fail
4200 $Html = $this->CheckOutputModification(
4201 $Html, $NewHtml,
4202 "alternate domain substitutions");
4203 }
4204 }
4205
4206 return $Html;
4207 }
4208
4227 private function FindFile($DirectoryList, $BaseName,
4228 $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
4229 {
4230 # generate template cache index for this page
4231 $CacheIndex = md5(serialize($DirectoryList))
4232 .":".self::$ActiveUI.":".$BaseName;
4233
4234 # if caching is enabled and we have cached location
4235 if (($this->TemplateLocationCacheInterval > 0)
4236 && array_key_exists($CacheIndex,
4237 $this->TemplateLocationCache))
4238 {
4239 # use template location from cache
4240 $FoundFileName = $this->TemplateLocationCache[$CacheIndex];
4241 }
4242 else
4243 {
4244 # if suffixes specified and base name does not include suffix
4245 if (count($PossibleSuffixes)
4246 && !preg_match("/\.[a-zA-Z0-9]+$/", $BaseName))
4247 {
4248 # add versions of file names with suffixes to file name list
4249 $FileNames = array();
4250 foreach ($PossibleSuffixes as $Suffix)
4251 {
4252 $FileNames[] = $BaseName.".".$Suffix;
4253 }
4254 }
4255 else
4256 {
4257 # use base name as file name
4258 $FileNames = array($BaseName);
4259 }
4260
4261 # if prefixes specified
4262 if (count($PossiblePrefixes))
4263 {
4264 # add versions of file names with prefixes to file name list
4265 $NewFileNames = array();
4266 foreach ($FileNames as $FileName)
4267 {
4268 foreach ($PossiblePrefixes as $Prefix)
4269 {
4270 $NewFileNames[] = $Prefix.$FileName;
4271 }
4272 }
4273 $FileNames = $NewFileNames;
4274 }
4275
4276 # for each possible location
4277 $FoundFileName = NULL;
4278 foreach ($DirectoryList as $Dir)
4279 {
4280 # substitute active or default UI name into path
4281 $Dir = str_replace(array("%ACTIVEUI%", "%DEFAULTUI%"),
4282 array(self::$ActiveUI, self::$DefaultUI), $Dir);
4283
4284 # for each possible file name
4285 foreach ($FileNames as $File)
4286 {
4287 # if template is found at location
4288 if (file_exists($Dir.$File))
4289 {
4290 # save full template file name and stop looking
4291 $FoundFileName = $Dir.$File;
4292 break 2;
4293 }
4294 }
4295 }
4296
4297 # save location in cache
4298 $this->TemplateLocationCache[$CacheIndex]
4299 = $FoundFileName;
4300
4301 # set flag indicating that cache should be saved
4302 $this->SaveTemplateLocationCache = TRUE;
4303 }
4304
4305 # return full template file name to caller
4306 return $FoundFileName;
4307 }
4308
4317 private function CompileScssFile($SrcFile)
4318 {
4319 # build path to CSS file
4320 $DstFile = self::$ScssCacheDir."/".dirname($SrcFile)
4321 ."/".basename($SrcFile);
4322 $DstFile = substr_replace($DstFile, "css", -4);
4323
4324 # if SCSS file is newer than CSS file
4325 if (!file_exists($DstFile)
4326 || (filemtime($SrcFile) > filemtime($DstFile)))
4327 {
4328 # attempt to create CSS cache subdirectory if not present
4329 if (!is_dir(dirname($DstFile)))
4330 {
4331 @mkdir(dirname($DstFile), 0777, TRUE);
4332 }
4333
4334 # if CSS cache directory and CSS file path appear writable
4335 static $CacheDirIsWritable;
4336 if (!isset($CacheDirIsWritable))
4337 { $CacheDirIsWritable = is_writable(self::$ScssCacheDir); }
4338 if (is_writable($DstFile)
4339 || (!file_exists($DstFile) && $CacheDirIsWritable))
4340 {
4341 # load SCSS and compile to CSS
4342 $ScssCode = file_get_contents($SrcFile);
4343 $ScssCompiler = new scssc();
4344 $ScssCompiler->setFormatter($this->GenerateCompactCss()
4345 ? "scss_formatter_compressed" : "scss_formatter");
4346 try
4347 {
4348 $CssCode = $ScssCompiler->compile($ScssCode);
4349
4350 # add fingerprinting for URLs in CSS
4351 $this->CssUrlFingerprintPath = dirname($SrcFile);
4352 $CssCode = preg_replace_callback(
4353 "/url\((['\"]*)(.+)\.([a-z]+)(['\"]*)\)/",
4354 array($this, "CssUrlFingerprintInsertion"),
4355 $CssCode);
4356
4357 # strip out comments from CSS (if requested)
4358 if ($this->GenerateCompactCss())
4359 {
4360 $CssCode = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!',
4361 '', $CssCode);
4362 }
4363
4364 # write out CSS file
4365 file_put_contents($DstFile, $CssCode);
4366 }
4367 catch (Exception $Ex)
4368 {
4369 $this->LogError(self::LOGLVL_ERROR,
4370 "Error compiling SCSS file ".$SrcFile.": "
4371 .$Ex->getMessage());
4372 $DstFile = NULL;
4373 }
4374 }
4375 else
4376 {
4377 # log error and set CSS file path to indicate failure
4378 $this->LogError(self::LOGLVL_ERROR,
4379 "Unable to write out CSS file (compiled from SCSS) to "
4380 .$DstFile);
4381 $DstFile = NULL;
4382 }
4383 }
4384
4385 # return CSS file path to caller
4386 return $DstFile;
4387 }
4388
4396 private function MinimizeJavascriptFile($SrcFile)
4397 {
4398 # bail out if file is on exclusion list
4399 foreach ($this->DoNotMinimizeList as $DNMFile)
4400 {
4401 if (($SrcFile == $DNMFile) || (basename($SrcFile) == $DNMFile))
4402 {
4403 return NULL;
4404 }
4405 }
4406
4407 # build path to minimized file
4408 $DstFile = self::$JSMinCacheDir."/".dirname($SrcFile)
4409 ."/".basename($SrcFile);
4410 $DstFile = substr_replace($DstFile, ".min", -3, 0);
4411
4412 # if original file is newer than minimized file
4413 if (!file_exists($DstFile)
4414 || (filemtime($SrcFile) > filemtime($DstFile)))
4415 {
4416 # attempt to create cache subdirectory if not present
4417 if (!is_dir(dirname($DstFile)))
4418 {
4419 @mkdir(dirname($DstFile), 0777, TRUE);
4420 }
4421
4422 # if cache directory and minimized file path appear writable
4423 static $CacheDirIsWritable;
4424 if (!isset($CacheDirIsWritable))
4425 { $CacheDirIsWritable = is_writable(self::$JSMinCacheDir); }
4426 if (is_writable($DstFile)
4427 || (!file_exists($DstFile) && $CacheDirIsWritable))
4428 {
4429 # load JavaScript code
4430 $Code = file_get_contents($SrcFile);
4431
4432 # decide which minimizer to use
4433 if ($this->JSMinimizerJavaScriptPackerAvailable
4434 && $this->JSMinimizerJShrinkAvailable)
4435 {
4436 $Minimizer = (strlen($Code) < 5000)
4437 ? "JShrink" : "JavaScriptPacker";
4438 }
4439 elseif ($this->JSMinimizerJShrinkAvailable)
4440 {
4441 $Minimizer = "JShrink";
4442 }
4443 else
4444 {
4445 $Minimizer = "NONE";
4446 }
4447
4448 # minimize code
4449 switch ($Minimizer)
4450 {
4451 case "JavaScriptMinimizer":
4452 $Packer = new JavaScriptPacker($Code, "Normal");
4453 $MinimizedCode = $Packer->pack();
4454 break;
4455
4456 case "JShrink":
4457 try
4458 {
4459 $MinimizedCode = \JShrink\Minifier::minify($Code);
4460 }
4461 catch (Exception $Exception)
4462 {
4463 unset($MinimizedCode);
4464 $MinimizeError = $Exception->getMessage();
4465 }
4466 break;
4467 }
4468
4469 # if minimization succeeded
4470 if (isset($MinimizedCode))
4471 {
4472 # write out minimized file
4473 file_put_contents($DstFile, $MinimizedCode);
4474 }
4475 else
4476 {
4477 # log error and set destination file path to indicate failure
4478 $ErrMsg = "Unable to minimize JavaScript file ".$SrcFile;
4479 if (isset($MinimizeError))
4480 {
4481 $ErrMsg .= " (".$MinimizeError.")";
4482 }
4483 $this->LogError(self::LOGLVL_ERROR, $ErrMsg);
4484 $DstFile = NULL;
4485 }
4486 }
4487 else
4488 {
4489 # log error and set destination file path to indicate failure
4490 $this->LogError(self::LOGLVL_ERROR,
4491 "Unable to write out minimized JavaScript to file ".$DstFile);
4492 $DstFile = NULL;
4493 }
4494 }
4495
4496 # return CSS file path to caller
4497 return $DstFile;
4498 }
4499
4507 private function CssUrlFingerprintInsertion($Matches)
4508 {
4509 # generate fingerprint string from CSS file modification time
4510 $FileName = realpath($this->CssUrlFingerprintPath."/".
4511 $Matches[2].".".$Matches[3]);
4512 $MTime = filemtime($FileName);
4513 $Fingerprint = sprintf("%06X", ($MTime % 0xFFFFFF));
4514
4515 # build URL string with fingerprint and return it to caller
4516 return "url(".$Matches[1].$Matches[2].".".$Fingerprint
4517 .".".$Matches[3].$Matches[4].")";
4518 }
4519
4526 private function GetRequiredFilesNotYetLoaded($PageContentFile)
4527 {
4528 # start out assuming no files required
4529 $RequiredFiles = array();
4530
4531 # if page content file supplied
4532 if ($PageContentFile)
4533 {
4534 # if file containing list of required files is available
4535 $Path = dirname($PageContentFile);
4536 $RequireListFile = $Path."/REQUIRES";
4537 if (file_exists($RequireListFile))
4538 {
4539 # read in list of required files
4540 $RequestedFiles = file($RequireListFile);
4541
4542 # for each line in required file list
4543 foreach ($RequestedFiles as $Line)
4544 {
4545 # if line is not a comment
4546 $Line = trim($Line);
4547 if (!preg_match("/^#/", $Line))
4548 {
4549 # if file has not already been loaded
4550 if (!in_array($Line, $this->FoundUIFiles))
4551 {
4552 # add to list of required files
4553 $RequiredFiles[] = $Line;
4554 }
4555 }
4556 }
4557 }
4558 }
4559
4560 # add in additional required files if any
4561 if (count($this->AdditionalRequiredUIFiles))
4562 {
4563 # make sure there are no duplicates
4564 $AdditionalRequiredUIFiles = array_unique(
4565 $this->AdditionalRequiredUIFiles);
4566
4567 $RequiredFiles = array_merge(
4568 $RequiredFiles, $AdditionalRequiredUIFiles);
4569 }
4570
4571 # return list of required files to caller
4572 return $RequiredFiles;
4573 }
4574
4585 private function GetUIFileLoadingTag($FileName, $AdditionalAttributes = NULL)
4586 {
4587 # pad additional attributes if supplied
4588 $AddAttribs = $AdditionalAttributes ? " ".$AdditionalAttributes : "";
4589
4590 # retrieve type of UI file
4591 $FileType = $this->GetFileType($FileName);
4592
4593 # construct tag based on file type
4594 switch ($FileType)
4595 {
4596 case self::FT_CSS:
4597 $Tag = "<link rel=\"stylesheet\" type=\"text/css\""
4598 ." media=\"all\" href=\"".$FileName."\""
4599 .$AddAttribs." />";
4600 break;
4601
4602 case self::FT_JAVASCRIPT:
4603 $Tag = "<script type=\"text/javascript\""
4604 ." src=\"".$FileName."\""
4605 .$AddAttribs."></script>";
4606 break;
4607
4608 default:
4609 $Tag = "";
4610 break;
4611 }
4612
4613 # return constructed tag to caller
4614 return $Tag;
4615 }
4616
4621 private function AutoloadObjects($ClassName)
4622 {
4623 # if caching is not turned off
4624 # and we have a cached location for class
4625 # and file at cached location is readable
4626 if ((self::$ObjectLocationCacheInterval > 0)
4627 && array_key_exists($ClassName,
4628 self::$ObjectLocationCache)
4629 && is_readable(self::$ObjectLocationCache[$ClassName]))
4630 {
4631 # use object location from cache
4632 require_once(self::$ObjectLocationCache[$ClassName]);
4633 }
4634 else
4635 {
4636 # convert any namespace separators in class name
4637 $ClassName = str_replace("\\", "-", $ClassName);
4638
4639 # for each possible object file directory
4640 static $FileLists;
4641 foreach (self::$ObjectDirectories as $Location => $Info)
4642 {
4643 # make any needed replacements in directory path
4644 $Location = str_replace(array("%ACTIVEUI%", "%DEFAULTUI%"),
4645 array(self::$ActiveUI, self::$DefaultUI), $Location);
4646
4647 # if directory looks valid
4648 if (is_dir($Location))
4649 {
4650 # build class file name
4651 $NewClassName = ($Info["ClassPattern"] && $Info["ClassReplacement"])
4652 ? preg_replace($Info["ClassPattern"],
4653 $Info["ClassReplacement"], $ClassName)
4654 : $ClassName;
4655
4656 # read in directory contents if not already retrieved
4657 if (!isset($FileLists[$Location]))
4658 {
4659 $FileLists[$Location] = self::ReadDirectoryTree(
4660 $Location, '/^.+\.php$/i');
4661 }
4662
4663 # for each file in target directory
4664 $FileNames = $FileLists[$Location];
4665 $TargetName = strtolower($Info["Prefix"].$NewClassName.".php");
4666 foreach ($FileNames as $FileName)
4667 {
4668 # if file matches our target object file name
4669 if (strtolower($FileName) == $TargetName)
4670 {
4671 # include object file
4672 require_once($Location.$FileName);
4673
4674 # save location to cache
4675 self::$ObjectLocationCache[$ClassName]
4676 = $Location.$FileName;
4677
4678 # set flag indicating that cache should be saved
4679 self::$SaveObjectLocationCache = TRUE;
4680
4681 # stop looking
4682 break 2;
4683 }
4684 }
4685 }
4686 }
4687 }
4688 }
4689
4697 private static function ReadDirectoryTree($Directory, $Pattern)
4698 {
4699 $CurrentDir = getcwd();
4700 chdir($Directory);
4701 $DirIter = new RecursiveDirectoryIterator(".");
4702 $IterIter = new RecursiveIteratorIterator($DirIter);
4703 $RegexResults = new RegexIterator($IterIter, $Pattern,
4704 RecursiveRegexIterator::GET_MATCH);
4705 $FileList = array();
4706 foreach ($RegexResults as $Result)
4707 {
4708 $FileList[] = substr($Result[0], 2);
4709 }
4710 chdir($CurrentDir);
4711 return $FileList;
4712 }
4713
4717 private function UndoMagicQuotes()
4718 {
4719 # if this PHP version has magic quotes support
4720 if (version_compare(PHP_VERSION, "5.4.0", "<"))
4721 {
4722 # turn off runtime magic quotes if on
4723 if (get_magic_quotes_runtime())
4724 {
4725 // @codingStandardsIgnoreStart
4726 set_magic_quotes_runtime(FALSE);
4727 // @codingStandardsIgnoreEnd
4728 }
4729
4730 # if magic quotes GPC is on
4731 if (get_magic_quotes_gpc())
4732 {
4733 # strip added slashes from incoming variables
4734 $GPC = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
4735 array_walk_recursive($GPC,
4736 array($this, "UndoMagicQuotes_StripCallback"));
4737 }
4738 }
4739 }
4744 private function UndoMagicQuotes_StripCallback(&$Value)
4745 {
4746 $Value = stripslashes($Value);
4747 }
4748
4753 private function LoadUIFunctions()
4754 {
4755 $Dirs = array(
4756 "local/interface/%ACTIVEUI%/include",
4757 "interface/%ACTIVEUI%/include",
4758 "local/interface/%DEFAULTUI%/include",
4759 "interface/%DEFAULTUI%/include",
4760 );
4761 foreach ($Dirs as $Dir)
4762 {
4763 $Dir = str_replace(array("%ACTIVEUI%", "%DEFAULTUI%"),
4764 array(self::$ActiveUI, self::$DefaultUI), $Dir);
4765 if (is_dir($Dir))
4766 {
4767 $FileNames = scandir($Dir);
4768 foreach ($FileNames as $FileName)
4769 {
4770 if (preg_match("/^F-([A-Za-z0-9_]+)\.php/",
4771 $FileName, $Matches)
4772 || preg_match("/^F-([A-Za-z0-9_]+)\.html/",
4773 $FileName, $Matches))
4774 {
4775 if (!function_exists($Matches[1]))
4776 {
4777 include_once($Dir."/".$FileName);
4778 }
4779 }
4780 }
4781 }
4782 }
4783 }
4784
4790 private function ProcessPeriodicEvent($EventName, $Callback)
4791 {
4792 # retrieve last execution time for event if available
4793 $Signature = self::GetCallbackSignature($Callback);
4794 $LastRun = $this->DB->Query("SELECT LastRunAt FROM PeriodicEvents"
4795 ." WHERE Signature = '".addslashes($Signature)."'", "LastRunAt");
4796
4797 # determine whether enough time has passed for event to execute
4798 $ShouldExecute = (($LastRun === NULL)
4799 || (time() > (strtotime($LastRun) + $this->EventPeriods[$EventName])))
4800 ? TRUE : FALSE;
4801
4802 # if event should run
4803 if ($ShouldExecute)
4804 {
4805 # add event to task queue
4806 $WrapperCallback = array("ApplicationFramework", "PeriodicEventWrapper");
4807 $WrapperParameters = array(
4808 $EventName, $Callback, array("LastRunAt" => $LastRun));
4809 $this->QueueUniqueTask($WrapperCallback, $WrapperParameters);
4810 }
4811
4812 # add event to list of periodic events
4813 $this->KnownPeriodicEvents[$Signature] = array(
4814 "Period" => $EventName,
4815 "Callback" => $Callback,
4816 "Queued" => $ShouldExecute);
4817 }
4818
4826 private static function PeriodicEventWrapper($EventName, $Callback, $Parameters)
4827 {
4828 static $DB;
4829 if (!isset($DB)) { $DB = new Database(); }
4830
4831 # run event
4832 $ReturnVal = call_user_func_array($Callback, $Parameters);
4833
4834 # if event is already in database
4835 $Signature = self::GetCallbackSignature($Callback);
4836 if ($DB->Query("SELECT COUNT(*) AS EventCount FROM PeriodicEvents"
4837 ." WHERE Signature = '".addslashes($Signature)."'", "EventCount"))
4838 {
4839 # update last run time for event
4840 $DB->Query("UPDATE PeriodicEvents SET LastRunAt = "
4841 .(($EventName == "EVENT_PERIODIC")
4842 ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
4843 : "NOW()")
4844 ." WHERE Signature = '".addslashes($Signature)."'");
4845 }
4846 else
4847 {
4848 # add last run time for event to database
4849 $DB->Query("INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES "
4850 ."('".addslashes($Signature)."', "
4851 .(($EventName == "EVENT_PERIODIC")
4852 ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
4853 : "NOW()").")");
4854 }
4855 }
4856
4862 private static function GetCallbackSignature($Callback)
4863 {
4864 return !is_array($Callback) ? $Callback
4865 : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
4866 ."::".$Callback[1];
4867 }
4868
4873 private function PrepForTSR()
4874 {
4875 # if HTML has been output and it's time to launch another task
4876 # (only TSR if HTML has been output because otherwise browsers
4877 # may misbehave after connection is closed)
4878 if ((PHP_SAPI != "cli")
4879 && ($this->JumpToPage || !$this->SuppressHTML)
4880 && (time() > (strtotime($this->Settings["LastTaskRunAt"])
4881 + ($this->MaxExecutionTime()
4882 / $this->Settings["MaxTasksRunning"]) + 5))
4883 && $this->GetTaskQueueSize()
4884 && $this->Settings["TaskExecutionEnabled"])
4885 {
4886 # begin buffering output for TSR
4887 ob_start();
4888
4889 # let caller know it is time to launch another task
4890 return TRUE;
4891 }
4892 else
4893 {
4894 # let caller know it is not time to launch another task
4895 return FALSE;
4896 }
4897 }
4898
4903 private function LaunchTSR()
4904 {
4905 # set headers to close out connection to browser
4906 if (!$this->NoTSR)
4907 {
4908 ignore_user_abort(TRUE);
4909 header("Connection: close");
4910 header("Content-Length: ".ob_get_length());
4911 }
4912
4913 # output buffered content
4914 while (ob_get_level()) { ob_end_flush(); }
4915 flush();
4916
4917 # write out any outstanding data and end HTTP session
4918 session_write_close();
4919
4920 # set flag indicating that we are now running in background
4921 $this->RunningInBackground = TRUE;
4922
4923 # handle garbage collection for session data
4924 if (isset($this->SessionStorage) &&
4925 (rand()/getrandmax()) <= $this->SessionGcProbability)
4926 {
4927 # determine when sessions will expire
4928 $ExpiredTime = strtotime("-". self::$SessionLifetime." seconds");
4929
4930 # iterate over files in the session directory with a DirectoryIterator
4931 # NB: we cannot use scandir() here because it reads the
4932 # entire list of files into memory and may exceed the memory
4933 # limit for directories with very many files
4934 $DI = new DirectoryIterator($this->SessionStorage);
4935 while ($DI->valid())
4936 {
4937 if ((strpos($DI->getFilename(), "sess_") === 0) &&
4938 $DI->isFile() &&
4939 $DI->getCTime() < $ExpiredTime)
4940 {
4941 unlink($DI->getPathname());
4942 }
4943 $DI->next();
4944 }
4945 }
4946
4947 # if there is still a task in the queue
4948 if ($this->GetTaskQueueSize())
4949 {
4950 # garbage collect to give as much memory as possible for tasks
4951 if (function_exists("gc_collect_cycles")) { gc_collect_cycles(); }
4952
4953 # turn on output buffering to (hopefully) record any crash output
4954 ob_start();
4955
4956 # lock tables and grab last task run time to double check
4957 $this->DB->Query("LOCK TABLES ApplicationFrameworkSettings WRITE");
4958 $this->LoadSettings();
4959
4960 # if still time to launch another task
4961 if (time() > (strtotime($this->Settings["LastTaskRunAt"])
4962 + ($this->MaxExecutionTime()
4963 / $this->Settings["MaxTasksRunning"]) + 5))
4964 {
4965 # update the "last run" time and release tables
4966 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
4967 ." SET LastTaskRunAt = '".date("Y-m-d H:i:s")."'");
4968 $this->DB->Query("UNLOCK TABLES");
4969
4970 # run tasks while there is a task in the queue
4971 # and enough time and memory left
4972 do
4973 {
4974 # run the next task
4975 $this->RunNextTask();
4976
4977 # calculate percentage of memory still available
4978 $PercentFreeMem = (self::GetFreeMemory()
4979 / self::GetPhpMemoryLimit()) * 100;
4980 }
4981 while ($this->GetTaskQueueSize()
4982 && ($this->GetSecondsBeforeTimeout() > 65)
4983 && ($PercentFreeMem > $this->BackgroundTaskMinFreeMemPercent));
4984 }
4985 else
4986 {
4987 # release tables
4988 $this->DB->Query("UNLOCK TABLES");
4989 }
4990 }
4991 }
4992
5002 private function GetTaskList($DBQuery, $Count, $Offset)
5003 {
5004 $this->DB->Query($DBQuery." LIMIT ".intval($Offset).",".intval($Count));
5005 $Tasks = array();
5006 while ($Row = $this->DB->FetchRow())
5007 {
5008 $Tasks[$Row["TaskId"]] = $Row;
5009 if ($Row["Callback"] ==
5010 serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
5011 {
5012 $WrappedCallback = unserialize($Row["Parameters"]);
5013 $Tasks[$Row["TaskId"]]["Callback"] = $WrappedCallback[1];
5014 $Tasks[$Row["TaskId"]]["Parameters"] = NULL;
5015 }
5016 else
5017 {
5018 $Tasks[$Row["TaskId"]]["Callback"] = unserialize($Row["Callback"]);
5019 $Tasks[$Row["TaskId"]]["Parameters"] = unserialize($Row["Parameters"]);
5020 }
5021 }
5022 return $Tasks;
5023 }
5024
5028 private function RunNextTask()
5029 {
5030 # lock tables to prevent same task from being run by multiple sessions
5031 $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
5032
5033 # look for task at head of queue
5034 $this->DB->Query("SELECT * FROM TaskQueue ORDER BY Priority, TaskId LIMIT 1");
5035 $Task = $this->DB->FetchRow();
5036
5037 # if there was a task available
5038 if ($Task)
5039 {
5040 # move task from queue to running tasks list
5041 $this->DB->Query("INSERT INTO RunningTasks "
5042 ."(TaskId,Callback,Parameters,Priority,Description) "
5043 ."SELECT * FROM TaskQueue WHERE TaskId = "
5044 .intval($Task["TaskId"]));
5045 $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = "
5046 .intval($Task["TaskId"]));
5047
5048 # release table locks to again allow other sessions to run tasks
5049 $this->DB->Query("UNLOCK TABLES");
5050
5051 # unpack stored task info
5052 $Callback = unserialize($Task["Callback"]);
5053 $Parameters = unserialize($Task["Parameters"]);
5054
5055 # attempt to load task callback if not already available
5056 $this->LoadFunction($Callback);
5057
5058 # save amount of free memory for later comparison
5059 $BeforeFreeMem = self::GetFreeMemory();
5060
5061 # run task
5062 $this->RunningTask = $Task;
5063 if ($Parameters)
5064 {
5065 call_user_func_array($Callback, $Parameters);
5066 }
5067 else
5068 {
5069 call_user_func($Callback);
5070 }
5071 unset($this->RunningTask);
5072
5073 # log if task leaked significant memory
5074 if (function_exists("gc_collect_cycles")) { gc_collect_cycles(); }
5075 $AfterFreeMem = self::GetFreeMemory();
5076 $LeakThreshold = self::GetPhpMemoryLimit()
5077 * ($this->BackgroundTaskMemLeakLogThreshold / 100);
5078 if (($BeforeFreeMem - $AfterFreeMem) > $LeakThreshold)
5079 {
5080 $this->LogError(self::LOGLVL_DEBUG, "Task "
5081 .self::GetTaskCallbackSynopsis(
5082 $this->GetTask($Task["TaskId"]))." leaked "
5083 .number_format($BeforeFreeMem - $AfterFreeMem)." bytes.");
5084 }
5085
5086 # remove task from running tasks list
5087 $this->DB->Query("DELETE FROM RunningTasks"
5088 ." WHERE TaskId = ".intval($Task["TaskId"]));
5089
5090 # prune running tasks list if necessary
5091 $RunningTasksCount = $this->DB->Query(
5092 "SELECT COUNT(*) AS TaskCount FROM RunningTasks", "TaskCount");
5093 if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
5094 {
5095 $this->DB->Query("DELETE FROM RunningTasks ORDER BY StartedAt"
5096 ." LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
5097 }
5098 }
5099 else
5100 {
5101 # release table locks to again allow other sessions to run tasks
5102 $this->DB->Query("UNLOCK TABLES");
5103 }
5104 }
5105
5111 public function OnCrash()
5112 {
5113 # attempt to remove any memory limits
5114 $FreeMemory = $this->GetFreeMemory();
5115 ini_set("memory_limit", -1);
5116
5117 # if there is a background task currently running
5118 if (isset($this->RunningTask))
5119 {
5120 # add info about current page load
5121 $CrashInfo["ElapsedTime"] = $this->GetElapsedExecutionTime();
5122 $CrashInfo["FreeMemory"] = $FreeMemory;
5123 $CrashInfo["REMOTE_ADDR"] = $_SERVER["REMOTE_ADDR"];
5124 $CrashInfo["REQUEST_URI"] = $_SERVER["REQUEST_URI"];
5125 if (isset($_SERVER["REQUEST_TIME"]))
5126 {
5127 $CrashInfo["REQUEST_TIME"] = $_SERVER["REQUEST_TIME"];
5128 }
5129 if (isset($_SERVER["REMOTE_HOST"]))
5130 {
5131 $CrashInfo["REMOTE_HOST"] = $_SERVER["REMOTE_HOST"];
5132 }
5133
5134 # add info about error that caused crash (if available)
5135 if (function_exists("error_get_last"))
5136 {
5137 $CrashInfo["LastError"] = error_get_last();
5138 }
5139
5140 # add info about current output buffer contents (if available)
5141 if (ob_get_length() !== FALSE)
5142 {
5143 $CrashInfo["OutputBuffer"] = ob_get_contents();
5144 }
5145
5146 # if backtrace info is available for the crash
5147 $Backtrace = debug_backtrace();
5148 if (count($Backtrace) > 1)
5149 {
5150 # discard the current context from the backtrace
5151 array_shift($Backtrace);
5152
5153 # add the backtrace to the crash info
5154 $CrashInfo["Backtrace"] = $Backtrace;
5155 }
5156 # else if saved backtrace info is available
5157 elseif (isset($this->SavedContext))
5158 {
5159 # add the saved backtrace to the crash info
5160 $CrashInfo["Backtrace"] = $this->SavedContext;
5161 }
5162
5163 # save crash info for currently running task
5164 $DB = new Database();
5165 $DB->Query("UPDATE RunningTasks SET CrashInfo = '"
5166 .addslashes(serialize($CrashInfo))
5167 ."' WHERE TaskId = ".intval($this->RunningTask["TaskId"]));
5168 }
5169
5170 print("\n");
5171 return;
5172 }
5173
5190 private function AddToDirList($DirList, $Dir, $SearchLast, $SkipSlashCheck)
5191 {
5192 # convert incoming directory to array of directories (if needed)
5193 $Dirs = is_array($Dir) ? $Dir : array($Dir);
5194
5195 # reverse array so directories are searched in specified order
5196 $Dirs = array_reverse($Dirs);
5197
5198 # for each directory
5199 foreach ($Dirs as $Location)
5200 {
5201 # make sure directory includes trailing slash
5202 if (!$SkipSlashCheck)
5203 {
5204 $Location = $Location
5205 .((substr($Location, -1) != "/") ? "/" : "");
5206 }
5207
5208 # remove directory from list if already present
5209 if (in_array($Location, $DirList))
5210 {
5211 $DirList = array_diff(
5212 $DirList, array($Location));
5213 }
5214
5215 # add directory to list of directories
5216 if ($SearchLast)
5217 {
5218 array_push($DirList, $Location);
5219 }
5220 else
5221 {
5222 array_unshift($DirList, $Location);
5223 }
5224 }
5225
5226 # return updated directory list to caller
5227 return $DirList;
5228 }
5229
5237 private function ArrayPermutations($Items, $Perms = array())
5238 {
5239 if (empty($Items))
5240 {
5241 $Result = array($Perms);
5242 }
5243 else
5244 {
5245 $Result = array();
5246 for ($Index = count($Items) - 1; $Index >= 0; --$Index)
5247 {
5248 $NewItems = $Items;
5249 $NewPerms = $Perms;
5250 list($Segment) = array_splice($NewItems, $Index, 1);
5251 array_unshift($NewPerms, $Segment);
5252 $Result = array_merge($Result,
5253 $this->ArrayPermutations($NewItems, $NewPerms));
5254 }
5255 }
5256 return $Result;
5257 }
5258
5265 private function OutputModificationCallbackShell($Matches)
5266 {
5267 # call previously-stored external function
5268 return call_user_func($this->OutputModificationCallbackInfo["Callback"],
5269 $Matches,
5270 $this->OutputModificationCallbackInfo["Pattern"],
5271 $this->OutputModificationCallbackInfo["Page"],
5272 $this->OutputModificationCallbackInfo["SearchPattern"]);
5273 }
5274
5283 private function CheckOutputModification($Original, $Modified, $ErrorInfo)
5284 {
5285 # if error was reported by regex engine
5286 if (preg_last_error() !== PREG_NO_ERROR)
5287 {
5288 # log error
5289 $this->LogError(self::LOGLVL_ERROR,
5290 "Error reported by regex engine when modifying output."
5291 ." (".$ErrorInfo.")");
5292
5293 # use unmodified version of output
5294 $OutputToUse = $Original;
5295 }
5296 # else if modification reduced output by more than threshold
5297 elseif ((strlen(trim($Modified)) / strlen(trim($Original)))
5298 < self::OUTPUT_MODIFICATION_THRESHOLD)
5299 {
5300 # log error
5301 $this->LogError(self::LOGLVL_WARNING,
5302 "Content reduced below acceptable threshold while modifying output."
5303 ." (".$ErrorInfo.")");
5304
5305 # use unmodified version of output
5306 $OutputToUse = $Original;
5307 }
5308 else
5309 {
5310 # use modified version of output
5311 $OutputToUse = $Modified;
5312 }
5313
5314 # return output to use to caller
5315 return $OutputToUse;
5316 }
5317
5319 const OUTPUT_MODIFICATION_THRESHOLD = 0.10;
5320
5330 private function UpdateSetting(
5331 $FieldName, $NewValue = DB_NOVALUE, $Persistent = TRUE)
5332 {
5333 static $LocalSettings;
5334 if ($NewValue !== DB_NOVALUE)
5335 {
5336 if ($Persistent)
5337 {
5338 $LocalSettings[$FieldName] = $this->DB->UpdateValue(
5339 "ApplicationFrameworkSettings",
5340 $FieldName, $NewValue, NULL, $this->Settings);
5341 }
5342 else
5343 {
5344 $LocalSettings[$FieldName] = $NewValue;
5345 }
5346 }
5347 elseif (!isset($LocalSettings[$FieldName]))
5348 {
5349 $LocalSettings[$FieldName] = $this->DB->UpdateValue(
5350 "ApplicationFrameworkSettings",
5351 $FieldName, $NewValue, NULL, $this->Settings);
5352 }
5353 return $LocalSettings[$FieldName];
5354 }
5355
5357 private $InterfaceDirList = array(
5358 "local/interface/%ACTIVEUI%/",
5359 "interface/%ACTIVEUI%/",
5360 "local/interface/%DEFAULTUI%/",
5361 "interface/%DEFAULTUI%/",
5362 );
5367 private $IncludeDirList = array(
5368 "local/interface/%ACTIVEUI%/include/",
5369 "interface/%ACTIVEUI%/include/",
5370 "interface/%ACTIVEUI%/objects/",
5371 "local/interface/%DEFAULTUI%/include/",
5372 "interface/%DEFAULTUI%/include/",
5373 "interface/%DEFAULTUI%/objects/",
5374 );
5376 private $ImageDirList = array(
5377 "local/interface/%ACTIVEUI%/images/",
5378 "interface/%ACTIVEUI%/images/",
5379 "local/interface/%DEFAULTUI%/images/",
5380 "interface/%DEFAULTUI%/images/",
5381 );
5383 private $FunctionDirList = array(
5384 "local/interface/%ACTIVEUI%/include/",
5385 "interface/%ACTIVEUI%/include/",
5386 "local/interface/%DEFAULTUI%/include/",
5387 "interface/%DEFAULTUI%/include/",
5388 "local/include/",
5389 "include/",
5390 );
5391
5392 const NOVALUE = ".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
5393
5394
5395 # ---- Page Caching (Internal Methods) -----------------------------------
5396
5402 private function CheckForCachedPage($PageName)
5403 {
5404 # assume no cached page will be found
5405 $CachedPage = NULL;
5406
5407 # if returning a cached page is allowed
5408 if ($this->CacheCurrentPage)
5409 {
5410 # get fingerprint for requested page
5411 $PageFingerprint = $this->GetPageFingerprint($PageName);
5412
5413 # look for matching page in cache in database
5414 $this->DB->Query("SELECT * FROM AF_CachedPages"
5415 ." WHERE Fingerprint = '".addslashes($PageFingerprint)."'");
5416
5417 # if matching page found
5418 if ($this->DB->NumRowsSelected())
5419 {
5420 # if cached page has expired
5421 $Row = $this->DB->FetchRow();
5422 $ExpirationTime = strtotime(
5423 "-".$this->PageCacheExpirationPeriod()." seconds");
5424 if (strtotime($Row["CachedAt"]) < $ExpirationTime)
5425 {
5426 # clear expired pages from cache
5427 $ExpirationTimestamp = date("Y-m-d H:i:s", $ExpirationTime);
5428 $this->DB->Query("DELETE CP, CPTI FROM AF_CachedPages CP,"
5429 ." AF_CachedPageTagInts CPTI"
5430 ." WHERE CP.CachedAt < '".$ExpirationTimestamp."'"
5431 ." AND CPTI.CacheId = CP.CacheId");
5432 $this->DB->Query("DELETE FROM AF_CachedPages "
5433 ." WHERE CachedAt < '".$ExpirationTimestamp."'");
5434 }
5435 else
5436 {
5437 # display cached page and exit
5438 $CachedPage = $Row["PageContent"];
5439 }
5440 }
5441 }
5442
5443 # return any cached page found to caller
5444 return $CachedPage;
5445 }
5446
5452 private function UpdatePageCache($PageName, $PageContent)
5453 {
5454 # if page caching is enabled and current page should be cached
5455 if ($this->PageCacheEnabled()
5456 && $this->CacheCurrentPage
5457 && ($PageName != "404"))
5458 {
5459 # if page content looks invalid
5460 if (strlen(trim(strip_tags($PageContent))) == 0)
5461 {
5462 # log error
5463 $LogMsg = "Page not cached because content was empty."
5464 ." (PAGE: ".$PageName.", URL: ".$this->FullUrl().")";
5465 $this->LogError(self::LOGLVL_ERROR, $LogMsg);
5466 }
5467 else
5468 {
5469 # save page to cache
5470 $PageFingerprint = $this->GetPageFingerprint($PageName);
5471 $this->DB->Query("INSERT INTO AF_CachedPages"
5472 ." (Fingerprint, PageContent) VALUES"
5473 ." ('".$this->DB->EscapeString($PageFingerprint)."', '"
5474 .$this->DB->EscapeString($PageContent)."')");
5475 $CacheId = $this->DB->LastInsertId();
5476
5477 # for each page cache tag that was added
5478 foreach ($this->PageCacheTags as $Tag => $Pages)
5479 {
5480 # if current page is in list for tag
5481 if (in_array("CURRENT", $Pages) || in_array($PageName, $Pages))
5482 {
5483 # look up tag ID
5484 $TagId = $this->GetPageCacheTagId($Tag);
5485
5486 # mark current page as associated with tag
5487 $this->DB->Query("INSERT INTO AF_CachedPageTagInts"
5488 ." (TagId, CacheId) VALUES "
5489 ." (".intval($TagId).", ".intval($CacheId).")");
5490 }
5491 }
5492 }
5493 }
5494 }
5495
5501 private function GetPageCacheTagId($Tag)
5502 {
5503 # if tag is a non-negative integer
5504 if (is_numeric($Tag) && ($Tag > 0) && (intval($Tag) == $Tag))
5505 {
5506 # generate ID
5507 $Id = self::PAGECACHETAGIDOFFSET + $Tag;
5508 }
5509 else
5510 {
5511 # look up ID in database
5512 $Id = $this->DB->Query("SELECT TagId FROM AF_CachedPageTags"
5513 ." WHERE Tag = '".addslashes($Tag)."'", "TagId");
5514
5515 # if ID was not found
5516 if ($Id === NULL)
5517 {
5518 # add tag to database
5519 $this->DB->Query("INSERT INTO AF_CachedPageTags"
5520 ." SET Tag = '".addslashes($Tag)."'");
5521 $Id = $this->DB->LastInsertId();
5522 }
5523 }
5524
5525 # return tag ID to caller
5526 return $Id;
5527 }
5528
5534 private function GetPageFingerprint($PageName)
5535 {
5536 # only get the environmental fingerprint once so that it is consistent
5537 # between page construction start and end
5538 static $EnvFingerprint;
5539 if (!isset($EnvFingerprint))
5540 {
5541 $EnvData = json_encode($_GET).json_encode($_POST);
5542
5543 # if alternate domain support is enabled
5544 if ($this->HtaccessSupport() && self::$RootUrlOverride !== NULL)
5545 {
5546 # and if we were accessed via an alternate domain
5547 $VHost = $_SERVER["SERVER_NAME"];
5548 if (isset($this->AlternateDomainPrefixes[$VHost]))
5549 {
5550 # then add the alternate domain that was used to our
5551 # environment data
5552 $EnvData .= $VHost;
5553 }
5554 }
5555
5556 $EnvFingerprint = md5($EnvData);
5557
5558 }
5559
5560 # build page fingerprint and return it to caller
5561 return $PageName."-".$EnvFingerprint;
5562 }
5563 }
ApplicationFramework\UrlFingerprintingEnabled
UrlFingerprintingEnabled($NewValue=DB_NOVALUE)
Get/set whether URL fingerprinting is enabled.
Definition: ApplicationFramework.php:418
ApplicationFramework\MaxTasks
MaxTasks($NewValue=DB_NOVALUE)
Get/set maximum number of tasks to have running simultaneously.
Definition: ApplicationFramework.php:2913
ApplicationFramework\LOGLVL_ERROR
const LOGLVL_ERROR
ERROR error logging level.
Definition: ApplicationFramework.php:2204
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:2386
ApplicationFramework\GetOrphanedTaskList
GetOrphanedTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently in queue.
Definition: ApplicationFramework.php:2795
ApplicationFramework\SuppressHTMLOutput
SuppressHTMLOutput($NewSetting=TRUE)
Suppress loading of HTML files.
Definition: ApplicationFramework.php:1159
ApplicationFramework\AddInterfaceDirectories
AddInterfaceDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for user interface (HTML/TPL) files.
Definition: ApplicationFramework.php:313
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:286
ApplicationFramework\GetScriptUrl
static GetScriptUrl()
Retrieve SCRIPT_URL server value, pulling it from elsewhere if that variable isn&#39;t set...
Definition: ApplicationFramework.php:3543
ApplicationFramework\LOGLVL_INFO
const LOGLVL_INFO
INFO error logging level.
Definition: ApplicationFramework.php:2193
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:364
ApplicationFramework\DoNotUrlFingerprint
DoNotUrlFingerprint($Pattern)
Specify file or file name pattern to exclude from URL fingerprinting.
Definition: ApplicationFramework.php:1610
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:2655
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:3344
ApplicationFramework\LOGLVL_FATAL
const LOGLVL_FATAL
FATAL error logging level.
Definition: ApplicationFramework.php:2209
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:1344
ApplicationFramework\GetCleanUrlForPath
GetCleanUrlForPath($Path)
Get the clean URL mapped for a path.
Definition: ApplicationFramework.php:3223
ApplicationFramework\PRIORITY_LOW
const PRIORITY_LOW
Lower priority.
Definition: ApplicationFramework.php:2610
ApplicationFramework\IsRunningInBackground
IsRunningInBackground()
Determine whether currently running inside a background task.
Definition: ApplicationFramework.php:3011
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:2727
ApplicationFramework\JavascriptMinimizationEnabled
JavascriptMinimizationEnabled($NewValue=DB_NOVALUE)
Get/set whether the application framework will attempt to generate minimized JavaScript.
Definition: ApplicationFramework.php:469
ApplicationFramework\LogFile
LogFile($NewValue=NULL)
Get/set log file name.
Definition: ApplicationFramework.php:2084
ApplicationFramework\GetCleanUrl
GetCleanUrl()
Get the clean URL for the current page if one is available.
Definition: ApplicationFramework.php:3318
ApplicationFramework\PAGECACHETAGIDOFFSET
const PAGECACHETAGIDOFFSET
Definition: ApplicationFramework.php:3933
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:3513
ApplicationFramework\ClearObjectLocationCache
ClearObjectLocationCache()
Clear object (class) file location cache.
Definition: ApplicationFramework.php:406
ApplicationFramework\GetLock
GetLock($LockName, $Wait=TRUE)
Get an exclusive ("write") lock on the specified name.
Definition: ApplicationFramework.php:3766
ApplicationFramework\RequireUIFile
RequireUIFile($FileName)
Add file to list of required UI files.
Definition: ApplicationFramework.php:1622
ApplicationFramework\FullUrl
static FullUrl()
Get current full URL, before any clean URL remapping and with any query string (e.g.
Definition: ApplicationFramework.php:3498
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:3484
ApplicationFramework\GetOrphanedTaskCount
GetOrphanedTaskCount()
Retrieve current number of orphaned tasks.
Definition: ApplicationFramework.php:2807
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:1184
ApplicationFramework\GetTaskQueueSize
GetTaskQueueSize($Priority=NULL)
Retrieve current number of tasks in queue.
Definition: ApplicationFramework.php:2715
StdLib\SortCompare
static SortCompare($A, $B)
Perform compare and return value appropriate for sort function callbacks.
Definition: StdLib.php:179
ApplicationFramework\RootUrlOverride
static RootUrlOverride($NewValue=self::NOVALUE)
Get/set root URL override.
Definition: ApplicationFramework.php:3466
ApplicationFramework\GetNextLowerBackgroundPriority
GetNextLowerBackgroundPriority($Priority=NULL)
Get next lower possible background task priority.
Definition: ApplicationFramework.php:3057
ApplicationFramework\UrlFingerprintingRewriteSupport
static UrlFingerprintingRewriteSupport()
Determine if rewrite support for URL fingerprinting is available.
Definition: ApplicationFramework.php:3386
ApplicationFramework\GetQueuedTaskCount
GetQueuedTaskCount($Callback=NULL, $Parameters=NULL, $Priority=NULL, $Description=NULL)
Get number of queued tasks that match supplied values.
Definition: ApplicationFramework.php:2746
ApplicationFramework\DeleteTask
DeleteTask($TaskId)
Remove task from task queues.
Definition: ApplicationFramework.php:2842
ApplicationFramework\LOGLVL_DEBUG
const LOGLVL_DEBUG
DEBUG error logging level.
Definition: ApplicationFramework.php:2187
ApplicationFramework\EVENTTYPE_NAMED
const EVENTTYPE_NAMED
Named result event type.
Definition: ApplicationFramework.php:2246
ApplicationFramework\EVENTTYPE_FIRST
const EVENTTYPE_FIRST
First response event type.
Definition: ApplicationFramework.php:2238
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:3572
ApplicationFramework\EVENTTYPE_DEFAULT
const EVENTTYPE_DEFAULT
Default event type.
Definition: ApplicationFramework.php:2226
ApplicationFramework\GetPageUrl
GetPageUrl()
Get the full URL to the page.
Definition: ApplicationFramework.php:1061
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: JShrink-Minifier.php:103
ApplicationFramework\IsStaticOnlyEvent
IsStaticOnlyEvent($EventName)
Report whether specified event only allows static callbacks.
Definition: ApplicationFramework.php:2512
ApplicationFramework\IsRegisteredEvent
IsRegisteredEvent($EventName)
Check if event has been registered (is available to be signaled).
Definition: ApplicationFramework.php:2284
ApplicationFramework\AddObjectDirectory
static AddObjectDirectory($Dir, $Prefix="", $ClassPattern=NULL, $ClassReplacement=NULL)
Add directory to be searched for object files when autoloading.
Definition: ApplicationFramework.php:225
ApplicationFramework\ReleaseLock
ReleaseLock($LockName)
Release lock with specified name.
Definition: ApplicationFramework.php:3839
ApplicationFramework\ScssSupportEnabled
ScssSupportEnabled($NewValue=DB_NOVALUE)
Get/set whether SCSS compilation support is enabled.
Definition: ApplicationFramework.php:430
ApplicationFramework\PageCacheEnabled
PageCacheEnabled($NewValue=DB_NOVALUE)
Enable/disable page caching.
Definition: ApplicationFramework.php:1756
ApplicationFramework\SignalEvent
SignalEvent($EventName, $Parameters=NULL)
Signal that an event has occured.
Definition: ApplicationFramework.php:2429
ApplicationFramework\BasePath
static BasePath()
Get current base path (usually the part after the host name).
Definition: ApplicationFramework.php:3526
ApplicationFramework\LOGLVL_TRACE
const LOGLVL_TRACE
TRACE error logging level.
Definition: ApplicationFramework.php:2182
ApplicationFramework\LOGLVL_WARNING
const LOGLVL_WARNING
WARNING error logging level.
Definition: ApplicationFramework.php:2198
ApplicationFramework\PRIORITY_MEDIUM
const PRIORITY_MEDIUM
Medium (default) priority.
Definition: ApplicationFramework.php:2608
ApplicationFramework\LogError
LogError($Level, $Msg)
Write error message to log.
Definition: ApplicationFramework.php:1954
ApplicationFramework\LogHighMemoryUsage
LogHighMemoryUsage($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether logging of high memory usage is enabled.
Definition: ApplicationFramework.php:1916
ApplicationFramework\ScssRewriteSupport
static ScssRewriteSupport()
Determine if SCSS rewrite support is available.
Definition: ApplicationFramework.php:3398
ApplicationFramework\OnCrash
OnCrash()
Called automatically at program termination to ensure output is written out.
Definition: ApplicationFramework.php:5111
ApplicationFramework\GetKnownPeriodicEvents
GetKnownPeriodicEvents()
Get list of known periodic events.
Definition: ApplicationFramework.php:2562
ApplicationFramework\EVENTTYPE_CHAIN
const EVENTTYPE_CHAIN
Result chaining event type.
Definition: ApplicationFramework.php:2232
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:1896
ApplicationFramework\ClearPageCacheForTag
ClearPageCacheForTag($Tag)
Clear all cached pages associated with specified tag.
Definition: ApplicationFramework.php:1818
ApplicationFramework\DoNotCacheCurrentPage
DoNotCacheCurrentPage()
Prevent the current page from being cached.
Definition: ApplicationFramework.php:1776
scssc
SCSS compiler written in PHP.
Definition: scssc.php:45
ApplicationFramework\GetSecondsBeforeTimeout
GetSecondsBeforeTimeout()
Get remaining available (PHP) execution time.
Definition: ApplicationFramework.php:1738
ApplicationFramework\TaskIsInQueue
TaskIsInQueue($Callback, $Parameters=NULL)
Check if task is already in queue or currently running.
Definition: ApplicationFramework.php:2692
ApplicationFramework\GetCurrentBackgroundPriority
GetCurrentBackgroundPriority()
Determine current priority if running in background.
Definition: ApplicationFramework.php:3021
ApplicationFramework\AddFunctionDirectories
AddFunctionDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for function ("F-") files.
Definition: ApplicationFramework.php:340
ApplicationFramework\AddPageCacheTag
AddPageCacheTag($Tag, $Pages=NULL)
Add caching tag for current page or specified pages.
Definition: ApplicationFramework.php:1787
ApplicationFramework\LoggingLevel
LoggingLevel($NewValue=DB_NOVALUE)
Get/set logging level.
Definition: ApplicationFramework.php:2066
ApplicationFramework\GetPageCacheInfo
GetPageCacheInfo()
Get page cache information.
Definition: ApplicationFramework.php:1847
ApplicationFramework\PageCacheExpirationPeriod
PageCacheExpirationPeriod($NewValue=DB_NOVALUE)
Get/set page cache expiration period in seconds.
Definition: ApplicationFramework.php:1767
ApplicationFramework\ORDER_MIDDLE
const ORDER_MIDDLE
Run hooked function after ORDER_FIRST and before ORDER_LAST events.
Definition: ApplicationFramework.php:2251
ApplicationFramework\OUTPUT_MODIFICATION_THRESHOLD
const OUTPUT_MODIFICATION_THRESHOLD
Threshold below which page output modifications are considered to have failed.
Definition: ApplicationFramework.php:5319
ApplicationFramework\RegisterEvent
RegisterEvent($EventsOrEventName, $EventType=NULL)
Register one or more events that may be signaled.
Definition: ApplicationFramework.php:2263
PHP
PHP
Definition: RSSClient--Test.php:12
ApplicationFramework\JsMinRewriteSupport
static JsMinRewriteSupport()
Determine if rewrite support for JavaScript minification is available.
Definition: ApplicationFramework.php:3410
ApplicationFramework\DownloadFile
DownloadFile($FilePath, $FileName=NULL, $MimeType=NULL)
Send specified file for download by user.
Definition: ApplicationFramework.php:3678
ApplicationFramework\GetPageName
GetPageName()
Get name of page being loaded.
Definition: ApplicationFramework.php:1018
ApplicationFramework\LogSlowPageLoads
LogSlowPageLoads($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether logging of long page load times is enabled.
Definition: ApplicationFramework.php:1879
DB_NOVALUE
const DB_NOVALUE
Definition: Database.php:1541
ApplicationFramework\CleanUrlIsMapped
CleanUrlIsMapped($Path)
Report whether clean URL has already been mapped.
Definition: ApplicationFramework.php:3202
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:1934
ApplicationFramework\SetJumpToPage
SetJumpToPage($Page, $Delay=0, $IsLiteral=FALSE)
Set URL of page to autoload after PHP page file is executed.
Definition: ApplicationFramework.php:1077
ApplicationFramework\MIN_GC_PROBABILITY
const MIN_GC_PROBABILITY
Definition: ApplicationFramework.php:3936
ApplicationFramework\PRIORITY_HIGH
const PRIORITY_HIGH
Highest priority.
Definition: ApplicationFramework.php:2606
ApplicationFramework\LoadFunction
LoadFunction($Callback)
Attempt to load code for function or method if not currently available.
Definition: ApplicationFramework.php:1679
ApplicationFramework\GetNextHigherBackgroundPriority
GetNextHigherBackgroundPriority($Priority=NULL)
Get next higher possible background task priority.
Definition: ApplicationFramework.php:3035
ApplicationFramework\GetRunningTaskList
GetRunningTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently in queue.
Definition: ApplicationFramework.php:2780
ApplicationFramework\FindCommonTemplate
FindCommonTemplate($BaseName)
Preserved for backward compatibility for use with code written prior to October 2012.
Definition: ApplicationFramework.php:3862
ApplicationFramework\EventWillNextRunAt
EventWillNextRunAt($EventName, $Callback)
Get date/time a periodic event will next run.
Definition: ApplicationFramework.php:2527
ApplicationFramework\SessionLifetime
static SessionLifetime($NewValue=NULL)
Get/set session timeout in seconds.
Definition: ApplicationFramework.php:3360
ApplicationFramework\DefaultUserInterface
static DefaultUserInterface($UIName=NULL)
Get/set name of current default user interface.
Definition: ApplicationFramework.php:1169
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:2321
ApplicationFramework\ClearPageCache
ClearPageCache()
Clear all pages from page cache.
Definition: ApplicationFramework.php:1833
ApplicationFramework\IsHookedEvent
IsHookedEvent($EventName)
Check if an event is registered and is hooked to.
Definition: ApplicationFramework.php:2296
ApplicationFramework\GetFreeMemory
static GetFreeMemory()
Get current amount of free memory.
Definition: ApplicationFramework.php:3601
ApplicationFramework\GenerateCompactCss
GenerateCompactCss($NewValue=DB_NOVALUE)
Get/set whether generating compact CSS (when compiling SCSS) is enabled.
Definition: ApplicationFramework.php:443
ApplicationFramework\FT_JAVASCRIPT
const FT_JAVASCRIPT
JavaScript file type.
Definition: ApplicationFramework.php:1669
ApplicationFramework\AddImageDirectories
AddImageDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for image files.
Definition: ApplicationFramework.php:258
ApplicationFramework\FT_CSS
const FT_CSS
CSS file type.
Definition: ApplicationFramework.php:1665
ApplicationFramework\HtmlCharset
HtmlCharset($NewSetting=NULL)
Get/set HTTP character encoding value.
Definition: ApplicationFramework.php:1116
ApplicationFramework\ORDER_FIRST
const ORDER_FIRST
Run hooked function first (i.e.
Definition: ApplicationFramework.php:2249
StdLib\SQL_DATE_FORMAT
const SQL_DATE_FORMAT
Format to feed to date() to get SQL-compatible date/time string.
Definition: StdLib.php:323
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:1555
ApplicationFramework\GetUncleanUrlForPath
GetUncleanUrlForPath($Path)
Get the unclean URL for mapped for a path.
Definition: ApplicationFramework.php:3268
ApplicationFramework\FT_IMAGE
const FT_IMAGE
Image (GIF/JPG/PNG) file type.
Definition: ApplicationFramework.php:1667
ApplicationFramework\UseMinimizedJavascript
UseMinimizedJavascript($NewValue=DB_NOVALUE)
Get/set whether minimized JavaScript will be searched for and used if found.
Definition: ApplicationFramework.php:456
ApplicationFramework\TemplateLocationCacheExpirationInterval
TemplateLocationCacheExpirationInterval($NewInterval=DB_NOVALUE)
Get/set UI template location cache expiration period in minutes.
Definition: ApplicationFramework.php:378
ApplicationFramework\ClearTemplateLocationCache
ClearTemplateLocationCache()
Clear template location cache.
Definition: ApplicationFramework.php:386
ApplicationFramework\GetFileType
static GetFileType($FileName)
Determine type of specified file based on the file name.
Definition: ApplicationFramework.php:1632
ApplicationFramework\GetUncleanUrl
GetUncleanUrl()
Get the unclean URL for the current page.
Definition: ApplicationFramework.php:3327
ApplicationFramework\DoNotMinimizeFile
DoNotMinimizeFile($File)
Specify file(s) to not attempt to minimize.
Definition: ApplicationFramework.php:1131
ApplicationFramework\RecordContextInCaseOfCrash
RecordContextInCaseOfCrash($BacktraceOptions=0, $BacktraceLimit=0)
Record the current execution context in case of crash.
Definition: ApplicationFramework.php:487
ApplicationFramework\JumpToPageIsSet
JumpToPageIsSet()
Report whether a page to autoload has been set.
Definition: ApplicationFramework.php:1102
ApplicationFramework\ORDER_LAST
const ORDER_LAST
Run hooked function last (i.e.
Definition: ApplicationFramework.php:2253
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:1147
ApplicationFramework\HtaccessSupport
static HtaccessSupport()
Determine if .htaccess files are enabled.
Definition: ApplicationFramework.php:3374
ApplicationFramework\GetTask
GetTask($TaskId)
Retrieve task info from queue (either running or queued tasks).
Definition: ApplicationFramework.php:2855
ApplicationFramework\TaskExecutionEnabled
TaskExecutionEnabled($NewValue=DB_NOVALUE)
Get/set whether automatic task execution is enabled.
Definition: ApplicationFramework.php:2903
ApplicationFramework\MaxExecutionTime
MaxExecutionTime($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set maximum PHP execution time.
Definition: ApplicationFramework.php:3648
ApplicationFramework\GetPhpMemoryLimit
static GetPhpMemoryLimit()
Get PHP memory limit in bytes.
Definition: ApplicationFramework.php:3611
ApplicationFramework\LoadPage
LoadPage($PageName)
Load page PHP and HTML/TPL files.
Definition: ApplicationFramework.php:506
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:1254
ApplicationFramework\AddEnvInclude
AddEnvInclude($FileName)
Add file to be included to set up environment.
Definition: ApplicationFramework.php:1366
ApplicationFramework\NOVALUE
const NOVALUE
Definition: ApplicationFramework.php:5392
ApplicationFramework\GetUserInterfaces
GetUserInterfaces($FilterExp=NULL)
Get list of available user interfaces and their labels.
Definition: ApplicationFramework.php:1203
ApplicationFramework\ReQueueOrphanedTask
ReQueueOrphanedTask($TaskId, $NewPriority=NULL)
Move orphaned task back into queue.
Definition: ApplicationFramework.php:2820
ApplicationFramework\GetLogEntries
GetLogEntries($Limit=0)
Get log entries, in reverse chronological order.
Definition: ApplicationFramework.php:2099
ApplicationFramework\GUIFile
GUIFile($FileName)
Search UI directories for specified image or CSS file and return name of correct file.
Definition: ApplicationFramework.php:1377
ApplicationFramework\QueueTask
QueueTask($Callback, $Parameters=NULL, $Priority=self::PRIORITY_LOW, $Description="")
Add task to queue.
Definition: ApplicationFramework.php:2626
ApplicationFramework\GetTaskCallbackSynopsis
static GetTaskCallbackSynopsis($TaskInfo)
Get printable synopsis for task callback.
Definition: ApplicationFramework.php:2925
ApplicationFramework\PUIFile
PUIFile($FileName)
Search UI directories for specified interface (image, CSS, JavaScript etc) file and print name of cor...
Definition: ApplicationFramework.php:1535
ApplicationFramework\LOGFILE_MAX_LINE_LENGTH
const LOGFILE_MAX_LINE_LENGTH
Maximum length for a line in the log file.
Definition: ApplicationFramework.php:2214
ApplicationFramework\GetPageLocation
GetPageLocation()
Get the URL path to the page without the base path, if present.
Definition: ApplicationFramework.php:1028
ApplicationFramework\ObjectLocationCacheExpirationInterval
ObjectLocationCacheExpirationInterval($NewInterval=DB_NOVALUE)
Get/set object file location cache expiration period in minutes.
Definition: ApplicationFramework.php:398
ApplicationFramework\GetElapsedExecutionTime
GetElapsedExecutionTime()
Get time elapsed since constructor was called.
Definition: ApplicationFramework.php:1729
ApplicationFramework\SetBrowserDetectionFunc
SetBrowserDetectionFunc($DetectionFunc)
Specify function to use to detect the web browser type.
Definition: ApplicationFramework.php:353
ApplicationFramework\RootUrl
static RootUrl()
Get portion of current URL through host name, with no trailing slash (e.g.
Definition: ApplicationFramework.php:3423
ApplicationFramework\AddCleanUrl
AddCleanUrl($Pattern, $Page, $GetVars=NULL, $Template=NULL)
Add clean URL mapping.
Definition: ApplicationFramework.php:3104
JavaScriptPacker
Definition: JavaScriptPacker.php:69
ApplicationFramework\PRIORITY_BACKGROUND
const PRIORITY_BACKGROUND
Lowest priority.
Definition: ApplicationFramework.php:2612
ApplicationFramework\LogMessage
LogMessage($Level, $Msg)
Write status message to log.
Definition: ApplicationFramework.php:1996
$Items
$Items
Definition: RSSClient--Test.php:26
ApplicationFramework\FT_OTHER
const FT_OTHER
File type other than CSS, image, or JavaScript.
Definition: ApplicationFramework.php:1663