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-2014 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu
8 #
9
14 class ApplicationFramework {
15
16 # ---- PUBLIC INTERFACE --------------------------------------------------
17 /*@(*/
19
24 function __construct()
25 {
26 # save execution start time
27 $this->ExecutionStartTime = microtime(TRUE);
28
29 # begin/restore PHP session
30 $SessionDomain = isset($_SERVER["SERVER_NAME"]) ? $_SERVER["SERVER_NAME"]
31 : isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"]
32 : php_uname("n");
33 if (is_writable(session_save_path()))
34 {
35 $SessionStorage = session_save_path()
36 ."/".self::$AppName."_".md5($SessionDomain.dirname(__FILE__));
37 if (!is_dir($SessionStorage)) { mkdir($SessionStorage, 0700 ); }
38 if (is_writable($SessionStorage))
39 {
40 # save params of our session storage as instance variables for later use
41 $this->SessionGcProbability =
42 ini_get("session.gc_probability") / ini_get("session.gc_divisor");
43 $this->SessionStorage = $SessionStorage;
44
45 # store our session files in a subdir to avoid
46 # accidentally sharing sessions with other CWIS installs
47 # on the same domain
48 session_save_path($SessionStorage);
49
50 # set PHP's gc not to run, as it doesn't handle subdirectories anyway
51 # instead, we'll do the cleanup as we run background tasks
52 ini_set("session.gc_probability", 0);
53 }
54 }
55 ini_set("session.gc_maxlifetime", self::$SessionLifetime);
56 session_set_cookie_params(
57 self::$SessionLifetime, "/", $SessionDomain);
58 session_start();
59
60 # set up default object file search locations
61 self::AddObjectDirectory("local/objects");
62 self::AddObjectDirectory("objects");
63 self::AddObjectDirectory("local/interface/%ACTIVEUI%/objects");
64 self::AddObjectDirectory("interface/%ACTIVEUI%/objects");
65 self::AddObjectDirectory("local/interface/default/objects");
66 self::AddObjectDirectory("interface/default/objects");
67
68 # set up object file autoloader
69 $this->SetUpObjectAutoloading();
70
71 # set up function to output any buffered text in case of crash
72 register_shutdown_function(array($this, "OnCrash"));
73
74 # set up our internal environment
75 $this->DB = new Database();
76
77 # set up our exception handler
78 set_exception_handler(array($this, "GlobalExceptionHandler"));
79
80 # perform any work needed to undo PHP magic quotes
81 $this->UndoMagicQuotes();
82
83 # load our settings from database
84 $this->LoadSettings();
85
86 # set PHP maximum execution time
87 $this->MaxExecutionTime($this->Settings["MaxExecTime"]);
88
89 # register events we handle internally
90 $this->RegisterEvent($this->PeriodicEvents);
91 $this->RegisterEvent($this->UIEvents);
92
93 # attempt to create SCSS cache directory if needed and it does not exist
94 if ($this->ScssSupportEnabled() && !is_dir(self::$ScssCacheDir))
95 { @mkdir(self::$ScssCacheDir, 0777, TRUE); }
96
97 # attempt to create minimized JS cache directory if needed and it does not exist
98 if ($this->UseMinimizedJavascript()
99 && $this->JavascriptMinimizationEnabled()
100 && !is_dir(self::$JSMinCacheDir))
101 {
102 @mkdir(self::$JSMinCacheDir, 0777, TRUE);
103 }
104 }
111 function __destruct()
112 {
113 # if template location cache is flagged to be saved
114 if ($this->SaveTemplateLocationCache)
115 {
116 # write template location cache out and update cache expiration
117 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
118 ." SET TemplateLocationCache = '"
119 .addslashes(serialize(
120 $this->TemplateLocationCache))."',"
121 ." TemplateLocationCacheExpiration = '"
122 .date("Y-m-d H:i:s",
123 $this->TemplateLocationCacheExpiration)."'");
124 }
125
126 # if object location cache is flagged to be saved
127 if (self::$SaveObjectLocationCache)
128 {
129 # write object location cache out and update cache expiration
130 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
131 ." SET ObjectLocationCache = '"
132 .addslashes(serialize(
133 self::$ObjectLocationCache))."',"
134 ." ObjectLocationCacheExpiration = '"
135 .date("Y-m-d H:i:s",
136 self::$ObjectLocationCacheExpiration)."'");
137 }
138 }
145 function GlobalExceptionHandler($Exception)
146 {
147 # display exception info
148 $Location = $Exception->getFile()."[".$Exception->getLine()."]";
149 ?><table width="100%" cellpadding="5"
150 style="border: 2px solid #666666; background: #CCCCCC;
151 font-family: Courier New, Courier, monospace;
152 margin-top: 10px;"><tr><td>
153 <div style="color: #666666;">
154 <span style="font-size: 150%;">
155 <b>Uncaught Exception</b></span><br />
156 <b>Message:</b> <i><?PHP print $Exception->getMessage(); ?></i><br />
157 <b>Location:</b> <i><?PHP print $Location; ?></i><br />
158 <b>Trace:</b>
159 <blockquote><pre><?PHP print $Exception->getTraceAsString();
160 ?></pre></blockquote>
161 </div>
162 </td></tr></table><?PHP
163
164 # log exception if possible
165 $LogMsg = "Uncaught exception (".$Exception->getMessage().").";
166 $this->LogError(self::LOGLVL_ERROR, $LogMsg);
167 }
184 static function AddObjectDirectory(
185 $Dir, $Prefix = "", $ClassPattern = NULL, $ClassReplacement = NULL)
186 {
187 # make sure directory has trailing slash
188 $Dir = $Dir.((substr($Dir, -1) != "/") ? "/" : "");
189
190 # add directory to directory list
191 self::$ObjectDirectories = array_merge(
192 array($Dir => array(
193 "Prefix" => $Prefix,
194 "ClassPattern" => $ClassPattern,
195 "ClassReplacement" => $ClassReplacement,
196 )),
197 self::$ObjectDirectories);
198 }
199
219 function AddImageDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
220 {
221 # add directories to existing image directory list
222 $this->ImageDirList = $this->AddToDirList(
223 $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
224 }
225
246 function AddIncludeDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
247 {
248 # add directories to existing image directory list
249 $this->IncludeDirList = $this->AddToDirList(
250 $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
251 }
252
272 function AddInterfaceDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
273 {
274 # add directories to existing image directory list
275 $this->InterfaceDirList = $this->AddToDirList(
276 $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
277 }
278
298 function AddFunctionDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
299 {
300 # add directories to existing image directory list
301 $this->FunctionDirList = $this->AddToDirList(
302 $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
303 }
304
310 function SetBrowserDetectionFunc($DetectionFunc)
311 {
312 $this->BrowserDetectFunc = $DetectionFunc;
313 }
314
321 function AddUnbufferedCallback($Callback, $Parameters=array())
322 {
323 if (is_callable($Callback))
324 {
325 $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
326 }
327 }
328
335 function TemplateLocationCacheExpirationInterval($NewInterval = DB_NOVALUE)
336 {
337 return $this->UpdateSetting("TemplateLocationCacheInterval", $NewInterval);
338 }
339
346 function ObjectLocationCacheExpirationInterval($NewInterval = DB_NOVALUE)
347 {
348 return $this->UpdateSetting("ObjectLocationCacheInterval", $NewInterval);
349 }
350
357 function UrlFingerprintingEnabled($NewValue = DB_NOVALUE)
358 {
359 return $this->UpdateSetting("UrlFingerprintingEnabled", $NewValue);
360 }
361
369 function ScssSupportEnabled($NewValue = DB_NOVALUE)
370 {
371 return $this->UpdateSetting("ScssSupportEnabled", $NewValue);
372 }
373
382 function GenerateCompactCss($NewValue = DB_NOVALUE)
383 {
384 return $this->UpdateSetting("GenerateCompactCss", $NewValue);
385 }
386
395 function UseMinimizedJavascript($NewValue = DB_NOVALUE)
396 {
397 return $this->UpdateSetting("UseMinimizedJavascript", $NewValue);
398 }
399
408 function JavascriptMinimizationEnabled($NewValue = DB_NOVALUE)
409 {
410 return $this->UpdateSetting("JavascriptMinimizationEnabled", $NewValue);
411 }
412
426 function RecordContextInCaseOfCrash(
427 $BacktraceOptions = 0, $BacktraceLimit = 0)
428 {
429 if (version_compare(PHP_VERSION, "5.4.0", ">="))
430 {
431 $this->SavedContext = debug_backtrace(
432 $BacktraceOptions, $BacktraceLimit);
433 }
434 else
435 {
436 $this->SavedContext = debug_backtrace($BacktraceOptions);
437 }
438 array_shift($this->SavedContext);
439 }
440
445 function LoadPage($PageName)
446 {
447 # perform any clean URL rewriting
448 $PageName = $this->RewriteCleanUrls($PageName);
449
450 # sanitize incoming page name and save local copy
451 $PageName = preg_replace("/[^a-zA-Z0-9_.-]/", "", $PageName);
452 $this->PageName = $PageName;
453
454 # if page caching is turned on
455 if ($this->PageCacheEnabled())
456 {
457 # if we have a cached page
458 $CachedPage = $this->CheckForCachedPage($PageName);
459 if ($CachedPage !== NULL)
460 {
461 # set header to indicate cache hit was found
462 header("X-ScoutAF-Cache: HIT");
463
464 # display cached page and exit
465 print $CachedPage;
466 return;
467 }
468 else
469 {
470 # set header to indicate no cache hit was found
471 header("X-ScoutAF-Cache: MISS");
472 }
473 }
474
475 # buffer any output from includes or PHP file
476 ob_start();
477
478 # include any files needed to set up execution environment
479 foreach ($this->EnvIncludes as $IncludeFile)
480 {
481 include($IncludeFile);
482 }
483
484 # signal page load
485 $this->SignalEvent("EVENT_PAGE_LOAD", array("PageName" => $PageName));
486
487 # signal PHP file load
488 $SignalResult = $this->SignalEvent("EVENT_PHP_FILE_LOAD", array(
489 "PageName" => $PageName));
490
491 # if signal handler returned new page name value
492 $NewPageName = $PageName;
493 if (($SignalResult["PageName"] != $PageName)
494 && strlen($SignalResult["PageName"]))
495 {
496 # if new page name value is page file
497 if (file_exists($SignalResult["PageName"]))
498 {
499 # use new value for PHP file name
500 $PageFile = $SignalResult["PageName"];
501 }
502 else
503 {
504 # use new value for page name
505 $NewPageName = $SignalResult["PageName"];
506 }
507
508 # update local copy of page name
509 $this->PageName = $NewPageName;
510 }
511
512 # if we do not already have a PHP file
513 if (!isset($PageFile))
514 {
515 # look for PHP file for page
516 $OurPageFile = "pages/".$NewPageName.".php";
517 $LocalPageFile = "local/pages/".$NewPageName.".php";
518 $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
519 : (file_exists($OurPageFile) ? $OurPageFile
520 : "pages/".$this->DefaultPage.".php");
521 }
522
523 # load PHP file
524 include($PageFile);
525
526 # save buffered output to be displayed later after HTML file loads
527 $PageOutput = ob_get_contents();
528 ob_end_clean();
529
530 # signal PHP file load is complete
531 ob_start();
532 $Context["Variables"] = get_defined_vars();
533 $this->SignalEvent("EVENT_PHP_FILE_LOAD_COMPLETE",
534 array("PageName" => $PageName, "Context" => $Context));
535 $PageCompleteOutput = ob_get_contents();
536 ob_end_clean();
537
538 # set up for possible TSR (Terminate and Stay Resident :))
539 $ShouldTSR = $this->PrepForTSR();
540
541 # if PHP file indicated we should autorefresh to somewhere else
542 if ($this->JumpToPage)
543 {
544 if (!strlen(trim($PageOutput)))
545 {
546 # if client supports HTTP/1.1, use a 303 as it is most accurate
547 if ($_SERVER["SERVER_PROTOCOL"] == "HTTP/1.1")
548 {
549 header("HTTP/1.1 303 See Other");
550 header("Location: ".$this->JumpToPage);
551 }
552 else
553 {
554 # if the request was an HTTP/1.0 GET or HEAD, then
555 # use a 302 response code.
556
557 # NB: both RFC 2616 (HTTP/1.1) and RFC1945 (HTTP/1.0)
558 # explicitly prohibit automatic redirection via a 302
559 # if the request was not GET or HEAD.
560 if ($_SERVER["SERVER_PROTOCOL"] == "HTTP/1.0" &&
561 ($_SERVER["REQUEST_METHOD"] == "GET" ||
562 $_SERVER["REQUEST_METHOD"] == "HEAD") )
563 {
564 header("HTTP/1.0 302 Found");
565 header("Location: ".$this->JumpToPage);
566 }
567
568 # otherwise, fall back to a meta refresh
569 else
570 {
571 print '<html><head><meta http-equiv="refresh" '
572 .'content="0; URL='.$this->JumpToPage.'">'
573 .'</head><body></body></html>';
574 }
575 }
576 }
577 }
578 # else if HTML loading is not suppressed
579 elseif (!$this->SuppressHTML)
580 {
581 # set content-type to get rid of diacritic errors
582 header("Content-Type: text/html; charset="
583 .$this->HtmlCharset, TRUE);
584
585 # load common HTML file (defines common functions) if available
586 $CommonHtmlFile = $this->FindFile($this->IncludeDirList,
587 "Common", array("tpl", "html"));
588 if ($CommonHtmlFile) { include($CommonHtmlFile); }
589
590 # load UI functions
591 $this->LoadUIFunctions();
592
593 # begin buffering content
594 ob_start();
595
596 # signal HTML file load
597 $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD", array(
598 "PageName" => $PageName));
599
600 # if signal handler returned new page name value
601 $NewPageName = $PageName;
602 $PageContentFile = NULL;
603 if (($SignalResult["PageName"] != $PageName)
604 && strlen($SignalResult["PageName"]))
605 {
606 # if new page name value is HTML file
607 if (file_exists($SignalResult["PageName"]))
608 {
609 # use new value for HTML file name
610 $PageContentFile = $SignalResult["PageName"];
611 }
612 else
613 {
614 # use new value for page name
615 $NewPageName = $SignalResult["PageName"];
616 }
617 }
618
619 # load page content HTML file if available
620 if ($PageContentFile === NULL)
621 {
622 $PageContentFile = $this->FindFile(
623 $this->InterfaceDirList, $NewPageName,
624 array("tpl", "html"));
625 }
626 if ($PageContentFile)
627 {
628 include($PageContentFile);
629 }
630 else
631 {
632 print "<h2>ERROR: No HTML/TPL template found"
633 ." for this page (".$NewPageName.").</h2>";
634 }
635
636 # signal HTML file load complete
637 $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD_COMPLETE");
638
639 # stop buffering and save output
640 $PageContentOutput = ob_get_contents();
641 ob_end_clean();
642
643 # load page start HTML file if available
644 ob_start();
645 $PageStartFile = $this->FindFile($this->IncludeDirList, "Start",
646 array("tpl", "html"), array("StdPage", "StandardPage"));
647 if ($PageStartFile) { include($PageStartFile); }
648 $PageStartOutput = ob_get_contents();
649 ob_end_clean();
650
651 # load page end HTML file if available
652 ob_start();
653 $PageEndFile = $this->FindFile($this->IncludeDirList, "End",
654 array("tpl", "html"), array("StdPage", "StandardPage"));
655 if ($PageEndFile) { include($PageEndFile); }
656 $PageEndOutput = ob_get_contents();
657 ob_end_clean();
658
659 # get list of any required files not loaded
660 $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
661
662 # if a browser detection function has been made available
663 if (is_callable($this->BrowserDetectFunc))
664 {
665 # call function to get browser list
666 $Browsers = call_user_func($this->BrowserDetectFunc);
667
668 # for each required file
669 $NewRequiredFiles = array();
670 foreach ($RequiredFiles as $File)
671 {
672 # if file name includes browser keyword
673 if (preg_match("/%BROWSER%/", $File))
674 {
675 # for each browser
676 foreach ($Browsers as $Browser)
677 {
678 # substitute in browser name and add to new file list
679 $NewRequiredFiles[] = preg_replace(
680 "/%BROWSER%/", $Browser, $File);
681 }
682 }
683 else
684 {
685 # add to new file list
686 $NewRequiredFiles[] = $File;
687 }
688 }
689 $RequiredFiles = $NewRequiredFiles;
690 }
691 else
692 {
693 # filter out any files with browser keyword in their name
694 $NewRequiredFiles = array();
695 foreach ($RequiredFiles as $File)
696 {
697 if (!preg_match("/%BROWSER%/", $File))
698 {
699 $NewRequiredFiles[] = $File;
700 }
701 }
702 $RequiredFiles = $NewRequiredFiles;
703 }
704
705 # for each required file
706 foreach ($RequiredFiles as $File)
707 {
708 # locate specific file to use
709 $FilePath = $this->GUIFile($File);
710
711 # if file was found
712 if ($FilePath)
713 {
714 # determine file type
715 $NamePieces = explode(".", $File);
716 $FileSuffix = strtolower(array_pop($NamePieces));
717
718 # add file to HTML output based on file type
719 $FilePath = htmlspecialchars($FilePath);
720 switch ($FileSuffix)
721 {
722 case "js":
723 $Tag = '<script type="text/javascript" src="'
724 .$FilePath.'"></script>';
725 $PageEndOutput = preg_replace(
726 "#</body>#i", $Tag."\n</body>", $PageEndOutput, 1);
727 break;
728
729 case "css":
730 $Tag = '<link rel="stylesheet" type="text/css"'
731 .' media="all" href="'.$FilePath.'">';
732 $PageStartOutput = preg_replace(
733 "#</head>#i", $Tag."\n</head>", $PageStartOutput, 1);
734 break;
735 }
736 }
737 }
738
739 # assemble full page
740 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
741
742 # perform any regular expression replacements in output
743 $FullPageOutput = preg_replace($this->OutputModificationPatterns,
744 $this->OutputModificationReplacements, $FullPageOutput);
745
746 # perform any callback replacements in output
747 foreach ($this->OutputModificationCallbacks as $Info)
748 {
749 $this->OutputModificationCallbackInfo = $Info;
750 $FullPageOutput = preg_replace_callback($Info["SearchPattern"],
751 array($this, "OutputModificationCallbackShell"),
752 $FullPageOutput);
753 }
754
755 # provide the opportunity to modify full page output
756 $SignalResult = $this->SignalEvent("EVENT_PAGE_OUTPUT_FILTER", array(
757 "PageOutput" => $FullPageOutput));
758 if (isset($SignalResult["PageOutput"])
759 && strlen($SignalResult["PageOutput"]))
760 {
761 $FullPageOutput = $SignalResult["PageOutput"];
762 }
763
764 # if relative paths may not work because we were invoked via clean URL
765 if ($this->CleanUrlRewritePerformed || self::WasUrlRewritten())
766 {
767 # if using the <base> tag is okay
768 $BaseUrl = $this->BaseUrl();
769 if ($this->UseBaseTag)
770 {
771 # add <base> tag to header
772 $PageStartOutput = preg_replace("%<head>%",
773 "<head><base href=\"".$BaseUrl."\" />",
774 $PageStartOutput);
775
776 # re-assemble full page with new header
777 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
778
779 # the absolute URL to the current page
780 $FullUrl = $BaseUrl . $this->GetPageLocation();
781
782 # make HREF attribute values with just a fragment ID
783 # absolute since they don't work with the <base> tag because
784 # they are relative to the current page/URL, not the site
785 # root
786 $FullPageOutput = preg_replace(
787 array("%href=\"(#[^:\" ]+)\"%i", "%href='(#[^:' ]+)'%i"),
788 array("href=\"".$FullUrl."$1\"", "href='".$FullUrl."$1'"),
789 $FullPageOutput);
790 }
791 else
792 {
793 # try to fix any relative paths throughout code
794 $FullPageOutput = preg_replace(array(
795 "%src=\"/?([^?*:;{}\\\\\" ]+)\.(js|css|gif|png|jpg)\"%i",
796 "%src='/?([^?*:;{}\\\\' ]+)\.(js|css|gif|png|jpg)'%i",
797 # don't rewrite HREF attributes that are just
798 # fragment IDs because they are relative to the
799 # current page/URL, not the site root
800 "%href=\"/?([^#][^:\" ]*)\"%i",
801 "%href='/?([^#][^:' ]*)'%i",
802 "%action=\"/?([^#][^:\" ]*)\"%i",
803 "%action='/?([^#][^:' ]*)'%i",
804 "%@import\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
805 "%@import\s+url\('/?([^:\" ]+)'\s*\)%i",
806 "%src:\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
807 "%src:\s+url\('/?([^:\" ]+)'\s*\)%i",
808 "%@import\s+\"/?([^:\" ]+)\"\s*%i",
809 "%@import\s+'/?([^:\" ]+)'\s*%i",
810 ),
811 array(
812 "src=\"".$BaseUrl."$1.$2\"",
813 "src=\"".$BaseUrl."$1.$2\"",
814 "href=\"".$BaseUrl."$1\"",
815 "href=\"".$BaseUrl."$1\"",
816 "action=\"".$BaseUrl."$1\"",
817 "action=\"".$BaseUrl."$1\"",
818 "@import url(\"".$BaseUrl."$1\")",
819 "@import url('".$BaseUrl."$1')",
820 "src: url(\"".$BaseUrl."$1\")",
821 "src: url('".$BaseUrl."$1')",
822 "@import \"".$BaseUrl."$1\"",
823 "@import '".$BaseUrl."$1'",
824 ),
825 $FullPageOutput);
826 }
827 }
828
829 # write out full page
830 print $FullPageOutput;
831
832 # update page cache for this page
833 $this->UpdatePageCache($PageName, $FullPageOutput);
834 }
835
836 # run any post-processing routines
837 foreach ($this->PostProcessingFuncs as $Func)
838 {
839 call_user_func_array($Func["FunctionName"], $Func["Arguments"]);
840 }
841
842 # write out any output buffered from page code execution
843 if (strlen($PageOutput))
844 {
845 if (!$this->SuppressHTML)
846 {
847 ?><table width="100%" cellpadding="5"
848 style="border: 2px solid #666666; background: #CCCCCC;
849 font-family: Courier New, Courier, monospace;
850 margin-top: 10px;"><tr><td><?PHP
851 }
852 if ($this->JumpToPage)
853 {
854 ?><div style="color: #666666;"><span style="font-size: 150%;">
855 <b>Page Jump Aborted</b></span>
856 (because of error or other unexpected output)<br />
857 <b>Jump Target:</b>
858 <i><?PHP print($this->JumpToPage); ?></i></div><?PHP
859 }
860 print $PageOutput;
861 if (!$this->SuppressHTML)
862 {
863 ?></td></tr></table><?PHP
864 }
865 }
866
867 # write out any output buffered from the page code execution complete signal
868 if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
869 {
870 print $PageCompleteOutput;
871 }
872
873 # execute callbacks that should not have their output buffered
874 foreach ($this->UnbufferedCallbacks as $Callback)
875 {
876 call_user_func_array($Callback[0], $Callback[1]);
877 }
878
879 # log high memory usage
880 if (function_exists("memory_get_peak_usage"))
881 {
882 $MemoryThreshold = ($this->HighMemoryUsageThreshold()
883 * $this->GetPhpMemoryLimit()) / 100;
884 if ($this->LogHighMemoryUsage()
885 && (memory_get_peak_usage() >= $MemoryThreshold))
886 {
887 $HighMemUsageMsg = "High peak memory usage ("
888 .intval(memory_get_peak_usage()).") for "
889 .$this->FullUrl()." from "
890 .$_SERVER["REMOTE_ADDR"];
891 $this->LogMessage(self::LOGLVL_INFO, $HighMemUsageMsg);
892 }
893 }
894
895 # log slow page loads
896 if ($this->LogSlowPageLoads()
897 && ($this->GetElapsedExecutionTime()
898 >= ($this->SlowPageLoadThreshold())))
899 {
900 $SlowPageLoadMsg = "Slow page load ("
901 .intval($this->GetElapsedExecutionTime())."s) for "
902 .$this->FullUrl()." from "
903 .$_SERVER["REMOTE_ADDR"];
904 $this->LogMessage(self::LOGLVL_INFO, $SlowPageLoadMsg);
905 }
906
907 # terminate and stay resident (TSR!) if indicated and HTML has been output
908 # (only TSR if HTML has been output because otherwise browsers will misbehave)
909 if ($ShouldTSR) { $this->LaunchTSR(); }
910 }
911
917 function GetPageName()
918 {
919 return $this->PageName;
920 }
921
927 function GetPageLocation()
928 {
929 # retrieve current URL
930 $Url = $this->GetScriptUrl();
931
932 # remove the base path if present
933 $BasePath = $this->Settings["BasePath"];
934 if (stripos($Url, $BasePath) === 0)
935 {
936 $Url = substr($Url, strlen($BasePath));
937 }
938
939 return $Url;
940 }
941
947 function GetPageUrl()
948 {
949 return self::BaseUrl() . $this->GetPageLocation();
950 }
951
960 function SetJumpToPage($Page, $IsLiteral = FALSE)
961 {
962 if (!is_null($Page)
963 && (!$IsLiteral)
964 && (strpos($Page, "?") === FALSE)
965 && ((strpos($Page, "=") !== FALSE)
966 || ((stripos($Page, ".php") === FALSE)
967 && (stripos($Page, ".htm") === FALSE)
968 && (strpos($Page, "/") === FALSE)))
969 && (stripos($Page, "http://") !== 0)
970 && (stripos($Page, "https://") !== 0))
971 {
972 $this->JumpToPage = self::BaseUrl() . "index.php?P=".$Page;
973 }
974 else
975 {
976 $this->JumpToPage = $Page;
977 }
978 }
979
984 function JumpToPageIsSet()
985 {
986 return ($this->JumpToPage === NULL) ? FALSE : TRUE;
987 }
988
998 function HtmlCharset($NewSetting = NULL)
999 {
1000 if ($NewSetting !== NULL) { $this->HtmlCharset = $NewSetting; }
1001 return $this->HtmlCharset;
1002 }
1003
1013 function DoNotMinimizeFile($File)
1014 {
1015 if (!is_array($File)) { $File = array($File); }
1016 $this->DoNotMinimizeList = array_merge($this->DoNotMinimizeList, $File);
1017 }
1018
1029 function UseBaseTag($NewValue = NULL)
1030 {
1031 if ($NewValue !== NULL) { $this->UseBaseTag = $NewValue ? TRUE : FALSE; }
1032 return $this->UseBaseTag;
1033 }
1034
1041 function SuppressHTMLOutput($NewSetting = TRUE)
1042 {
1043 $this->SuppressHTML = $NewSetting;
1044 }
1045
1052 static function ActiveUserInterface($UIName = NULL)
1053 {
1054 if ($UIName !== NULL)
1055 {
1056 self::$ActiveUI = preg_replace("/^SPTUI--/", "", $UIName);
1057 }
1058 return self::$ActiveUI;
1059 }
1060
1066 function GetUserInterfaces()
1067 {
1068 # possible UI directories
1069 $InterfaceDirs = array(
1070 "interface",
1071 "local/interface");
1072
1073 # start out with an empty list
1074 $Interfaces = array();
1075
1076 # for each possible UI directory
1077 foreach ($InterfaceDirs as $InterfaceDir)
1078 {
1079 $Dir = dir($InterfaceDir);
1080
1081 # for each file in current directory
1082 while (($DirEntry = $Dir->read()) !== FALSE)
1083 {
1084 $InterfacePath = $InterfaceDir."/".$DirEntry;
1085
1086 # skip anything that doesn't have a name in the required format
1087 if (!preg_match('/^[a-zA-Z0-9]+$/', $DirEntry))
1088 {
1089 continue;
1090 }
1091
1092 # skip anything that isn't a directory
1093 if (!is_dir($InterfacePath))
1094 {
1095 continue;
1096 }
1097
1098 # read the UI name (if available)
1099 $UIName = @file_get_contents($InterfacePath."/NAME");
1100
1101 # use the directory name if the UI name isn't available
1102 if ($UIName === FALSE || !strlen($UIName))
1103 {
1104 $UIName = $DirEntry;
1105 }
1106
1107 $Interfaces[$InterfacePath] = $UIName;
1108 }
1109
1110 $Dir->close();
1111 }
1112
1113 # return list to caller
1114 return $Interfaces;
1115 }
1116
1141 function AddPostProcessingCall($FunctionName,
1142 &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
1143 &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
1144 &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
1145 {
1146 $FuncIndex = count($this->PostProcessingFuncs);
1147 $this->PostProcessingFuncs[$FuncIndex]["FunctionName"] = $FunctionName;
1148 $this->PostProcessingFuncs[$FuncIndex]["Arguments"] = array();
1149 $Index = 1;
1150 while (isset(${"Arg".$Index}) && (${"Arg".$Index} !== self::NOVALUE))
1151 {
1152 $this->PostProcessingFuncs[$FuncIndex]["Arguments"][$Index]
1153 =& ${"Arg".$Index};
1154 $Index++;
1155 }
1156 }
1157
1163 function AddEnvInclude($FileName)
1164 {
1165 $this->EnvIncludes[] = $FileName;
1166 }
1167
1174 function GUIFile($FileName)
1175 {
1176 # determine which location to search based on file suffix
1177 $FileType = $this->GetFileType($FileName);
1178 $DirList = ($FileType == self::FT_IMAGE)
1179 ? $this->ImageDirList : $this->IncludeDirList;
1180
1181 # if directed to use minimized JavaScript file
1182 if (($FileType == self::FT_JAVASCRIPT) && $this->UseMinimizedJavascript())
1183 {
1184 # look for minimized version of file
1185 $MinimizedFileName = substr_replace($FileName, ".min", -3, 0);
1186 $FoundFileName = $this->FindFile($DirList, $MinimizedFileName);
1187
1188 # if minimized file was not found
1189 if (is_null($FoundFileName))
1190 {
1191 # look for unminimized file
1192 $FoundFileName = $this->FindFile($DirList, $FileName);
1193
1194 # if unminimized file found
1195 if (!is_null($FoundFileName))
1196 {
1197 # if minimization enabled and supported
1198 if ($this->JavascriptMinimizationEnabled()
1199 && isset($_SERVER["JSMIN_REWRITE_SUPPORT"]))
1200 {
1201 # attempt to create minimized file
1202 $MinFileName = $this->MinimizeJavascriptFile(
1203 $FoundFileName);
1204
1205 # if minimization succeeded
1206 if ($MinFileName !== NULL)
1207 {
1208 # use minimized version
1209 $FoundFileName = $MinFileName;
1210
1211 # save file modification time if needed for fingerprinting
1212 if ($this->UrlFingerprintingEnabled())
1213 {
1214 $FileMTime = filemtime($FoundFileName);
1215 }
1216
1217 # strip off the cache location, allowing .htaccess
1218 # to handle that for us
1219 $FoundFileName = str_replace(
1220 self::$JSMinCacheDir."/", "", $FoundFileName);
1221 }
1222 }
1223 }
1224 }
1225 }
1226 # else if directed to use SCSS files
1227 elseif (($FileType == self::FT_CSS) && $this->ScssSupportEnabled())
1228 {
1229 # look for SCSS version of file
1230 $SourceFileName = preg_replace("/.css$/", ".scss", $FileName);
1231 $FoundSourceFileName = $this->FindFile($DirList, $SourceFileName);
1232
1233 # if SCSS file not found
1234 if ($FoundSourceFileName === NULL)
1235 {
1236 # look for CSS file
1237 $FoundFileName = $this->FindFile($DirList, $FileName);
1238 }
1239 else
1240 {
1241 # compile SCSS file (if updated) and return resulting CSS file
1242 $FoundFileName = $this->CompileScssFile($FoundSourceFileName);
1243
1244 # save file modification time if needed for fingerprinting
1245 if ($this->UrlFingerprintingEnabled())
1246 {
1247 $FileMTime = filemtime($FoundFileName);
1248 }
1249
1250 # strip off the cache location, allowing .htaccess to handle that for us
1251 if (isset($_SERVER["SCSS_REWRITE_SUPPORT"]))
1252 {
1253 $FoundFileName = str_replace(
1254 self::$ScssCacheDir."/", "", $FoundFileName);
1255 }
1256 }
1257 }
1258 # otherwise just search for the file
1259 else
1260 {
1261 $FoundFileName = $this->FindFile($DirList, $FileName);
1262 }
1263
1264 # add non-image files to list of found files (used for required files loading)
1265 if ($FileType != self::FT_IMAGE)
1266 { $this->FoundUIFiles[] = basename($FoundFileName); }
1267
1268 # if UI file fingerprinting is enabled and supported
1269 if ($this->UrlFingerprintingEnabled()
1270 && array_key_exists("URL_FINGERPRINTING_SUPPORT", $_SERVER)
1271 && $_SERVER["URL_FINGERPRINTING_SUPPORT"]
1272 && (isset($FileMTime) || file_exists($FoundFileName)))
1273 {
1274 # if file does not appear to be a server-side inclusion
1275 if (!preg_match('/\.(html|php)$/i', $FoundFileName))
1276 {
1277 # for each URL fingerprinting blacklist entry
1278 $OnBlacklist = FALSE;
1279 foreach ($this->UrlFingerprintBlacklist as $BlacklistEntry)
1280 {
1281 # if entry looks like a regular expression pattern
1282 if ($BlacklistEntry[0] == substr($BlacklistEntry, -1))
1283 {
1284 # check file name against regular expression
1285 if (preg_match($BlacklistEntry, $FoundFileName))
1286 {
1287 $OnBlacklist = TRUE;
1288 break;
1289 }
1290 }
1291 else
1292 {
1293 # check file name directly against entry
1294 if (basename($FoundFileName) == $BlacklistEntry)
1295 {
1296 $OnBlacklist = TRUE;
1297 break;
1298 }
1299 }
1300 }
1301
1302 # if file was not on blacklist
1303 if (!$OnBlacklist)
1304 {
1305 # get file modification time if not already retrieved
1306 if (!isset($FileMTime))
1307 {
1308 $FileMTime = filemtime($FoundFileName);
1309 }
1310
1311 # add timestamp fingerprint to file name
1312 $Fingerprint = sprintf("%06X",
1313 ($FileMTime % 0xFFFFFF));
1314 $FoundFileName = preg_replace("/^(.+)\.([a-z]+)$/",
1315 "$1.".$Fingerprint.".$2",
1316 $FoundFileName);
1317 }
1318 }
1319 }
1320
1321 # return file name to caller
1322 return $FoundFileName;
1323 }
1324
1333 function PUIFile($FileName)
1334 {
1335 $FullFileName = $this->GUIFile($FileName);
1336 if ($FullFileName) { print($FullFileName); }
1337 }
1338
1353 function IncludeUIFile($FileNames, $AdditionalAttributes = NULL)
1354 {
1355 # convert file name to array if necessary
1356 if (!is_array($FileNames)) { $FileNames = array($FileNames); }
1357
1358 # pad additional attributes if supplied
1359 $AddAttribs = $AdditionalAttributes ? " ".$AdditionalAttributes : "";
1360
1361 # for each file
1362 foreach ($FileNames as $BaseFileName)
1363 {
1364 # retrieve full file name
1365 $FileName = $this->GUIFile($BaseFileName);
1366
1367 # if file was found
1368 if ($FileName)
1369 {
1370 # print appropriate tag based on file type
1371 $FileType = $this->GetFileType($FileName);
1372 switch ($FileType)
1373 {
1374 case self::FT_CSS:
1375 print "<link rel=\"stylesheet\" type=\"text/css\""
1376 ." media=\"all\" href=\"".$FileName."\""
1377 .$AdditionalAttributes." />";
1378 $OverrideFileName = preg_replace(
1379 "/\.(css|scss)$/", "-Override.$1",
1380 $BaseFileName);
1381 $this->IncludeUIFile($OverrideFileName,
1382 $AdditionalAttributes);
1383 break;
1384
1385 case self::FT_JAVASCRIPT:
1386 print "<script type=\"text/javascript\""
1387 ." src=\"".$FileName."\""
1388 .$AddAttribs."></script>";
1389 $OverrideFileName = preg_replace(
1390 "/\.js$/", "-Override.js",
1391 $BaseFileName);
1392 $this->IncludeUIFile($OverrideFileName,
1393 $AdditionalAttributes);
1394 break;
1395
1396 case self::FT_IMAGE:
1397 break;
1398 }
1399 }
1400 }
1401 }
1402
1409 function DoNotUrlFingerprint($Pattern)
1410 {
1411 $this->UrlFingerprintBlacklist[] = $Pattern;
1412 }
1413
1421 function RequireUIFile($FileName)
1422 {
1423 $this->AdditionalRequiredUIFiles[] = $FileName;
1424 }
1425
1431 static function GetFileType($FileName)
1432 {
1433 $FileSuffix = strtolower(substr($FileName, -3));
1434 if ($FileSuffix == "css") { return self::FT_CSS; }
1435 elseif ($FileSuffix == ".js") { return self::FT_JAVASCRIPT; }
1436 elseif (($FileSuffix == "gif")
1437 || ($FileSuffix == "jpg")
1438 || ($FileSuffix == "png")) { return self::FT_IMAGE; }
1439 return self::FT_OTHER;
1440 }
1442 const FT_OTHER = 0;
1444 const FT_CSS = 1;
1446 const FT_IMAGE = 2;
1448 const FT_JAVASCRIPT = 3;
1449
1458 function LoadFunction($Callback)
1459 {
1460 # if specified function is not currently available
1461 if (!is_callable($Callback))
1462 {
1463 # if function info looks legal
1464 if (is_string($Callback) && strlen($Callback))
1465 {
1466 # start with function directory list
1467 $Locations = $this->FunctionDirList;
1468
1469 # add object directories to list
1470 $Locations = array_merge(
1471 $Locations, array_keys(self::$ObjectDirectories));
1472
1473 # look for function file
1474 $FunctionFileName = $this->FindFile($Locations, "F-".$Callback,
1475 array("php", "html"));
1476
1477 # if function file was found
1478 if ($FunctionFileName)
1479 {
1480 # load function file
1481 include_once($FunctionFileName);
1482 }
1483 else
1484 {
1485 # log error indicating function load failed
1486 $this->LogError(self::LOGLVL_ERROR, "Unable to load function"
1487 ." for callback \"".$Callback."\".");
1488 }
1489 }
1490 else
1491 {
1492 # log error indicating specified function info was bad
1493 $this->LogError(self::LOGLVL_ERROR, "Unloadable callback value"
1494 ." (".$Callback.")"
1495 ." passed to AF::LoadFunction().");
1496 }
1497 }
1498
1499 # report to caller whether function load succeeded
1500 return is_callable($Callback);
1501 }
1502
1507 function GetElapsedExecutionTime()
1508 {
1509 return microtime(TRUE) - $this->ExecutionStartTime;
1510 }
1511
1516 function GetSecondsBeforeTimeout()
1517 {
1518 return ini_get("max_execution_time") - $this->GetElapsedExecutionTime();
1519 }
1520
1521 /*@)*/ /* Application Framework */
1522
1523
1524 # ---- Page Caching ------------------------------------------------------
1525 /*@(*/
1527
1534 function PageCacheEnabled($NewValue = DB_NOVALUE)
1535 {
1536 return $this->UpdateSetting("PageCacheEnabled", $NewValue);
1537 }
1538
1545 function PageCacheExpirationPeriod($NewValue = DB_NOVALUE)
1546 {
1547 return $this->UpdateSetting("PageCacheExpirationPeriod", $NewValue);
1548 }
1549
1554 function DoNotCacheCurrentPage()
1555 {
1556 $this->CacheCurrentPage = FALSE;
1557 }
1558
1565 function AddPageCacheTag($Tag, $Pages = NULL)
1566 {
1567 # normalize tag
1568 $Tag = strtolower($Tag);
1569
1570 # if pages were supplied
1571 if ($Pages !== NULL)
1572 {
1573 # add pages to list for this tag
1574 if (isset($this->PageCacheTags[$Tag]))
1575 {
1576 $this->PageCacheTags[$Tag] = array_merge(
1577 $this->PageCacheTags[$Tag], $Pages);
1578 }
1579 else
1580 {
1581 $this->PageCacheTags[$Tag] = $Pages;
1582 }
1583 }
1584 else
1585 {
1586 # add current page to list for this tag
1587 $this->PageCacheTags[$Tag][] = "CURRENT";
1588 }
1589 }
1590
1596 function ClearPageCacheForTag($Tag)
1597 {
1598 # get tag ID
1599 $TagId = $this->GetPageCacheTagId($Tag);
1600
1601 # delete pages and tag/page connections for specified tag
1602 $this->DB->Query("DELETE CP, CPTI"
1603 ." FROM AF_CachedPages CP, AF_CachedPageTagInts CPTI"
1604 ." WHERE CPTI.TagId = ".intval($TagId)
1605 ." AND CP.CacheId = CPTI.CacheId");
1606 }
1607
1611 function ClearPageCache()
1612 {
1613 # clear all page cache tables
1614 $this->DB->Query("TRUNCATE TABLE AF_CachedPages");
1615 $this->DB->Query("TRUNCATE TABLE AF_CachedPageTags");
1616 $this->DB->Query("TRUNCATE TABLE AF_CachedPageTagInts");
1617 }
1618
1625 function GetPageCacheInfo()
1626 {
1627 $Length = $this->DB->Query("SELECT COUNT(*) AS CacheLen"
1628 ." FROM AF_CachedPages", "CacheLen");
1629 $Oldest = $this->DB->Query("SELECT CachedAt FROM AF_CachedPages"
1630 ." ORDER BY CachedAt ASC LIMIT 1", "CachedAt");
1631 return array(
1632 "NumberOfEntries" => $Length,
1633 "OldestTimestamp" => strtotime($Oldest),
1634 );
1635 }
1636
1637 /*@)*/ /* Page Caching */
1638
1639
1640 # ---- Logging -----------------------------------------------------------
1641 /*@(*/
1643
1654 function LogSlowPageLoads($NewValue = DB_NOVALUE)
1655 {
1656 return $this->UpdateSetting("LogSlowPageLoads", $NewValue);
1657 }
1658
1666 function SlowPageLoadThreshold($NewValue = DB_NOVALUE)
1667 {
1668 return $this->UpdateSetting("SlowPageLoadThreshold", $NewValue);
1669 }
1670
1681 function LogHighMemoryUsage($NewValue = DB_NOVALUE)
1682 {
1683 return $this->UpdateSetting("LogHighMemoryUsage", $NewValue);
1684 }
1685
1694 function HighMemoryUsageThreshold($NewValue = DB_NOVALUE)
1695 {
1696 return $this->UpdateSetting("HighMemoryUsageThreshold", $NewValue);
1697 }
1698
1712 function LogError($Level, $Msg)
1713 {
1714 # if error level is at or below current logging level
1715 if ($this->Settings["LoggingLevel"] >= $Level)
1716 {
1717 # attempt to log error message
1718 $Result = $this->LogMessage($Level, $Msg);
1719
1720 # if logging attempt failed and level indicated significant error
1721 if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
1722 {
1723 # throw exception about inability to log error
1724 static $AlreadyThrewException = FALSE;
1725 if (!$AlreadyThrewException)
1726 {
1727 $AlreadyThrewException = TRUE;
1728 throw new Exception("Unable to log error (".$Level.": ".$Msg
1729 .") to ".$this->LogFileName);
1730 }
1731 }
1732
1733 # report to caller whether message was logged
1734 return $Result;
1735 }
1736 else
1737 {
1738 # report to caller that message was not logged
1739 return FALSE;
1740 }
1741 }
1742
1754 function LogMessage($Level, $Msg)
1755 {
1756 # if message level is at or below current logging level
1757 if ($this->Settings["LoggingLevel"] >= $Level)
1758 {
1759 # attempt to open log file
1760 $FHndl = @fopen($this->LogFileName, "a");
1761
1762 # if log file could not be open
1763 if ($FHndl === FALSE)
1764 {
1765 # report to caller that message was not logged
1766 return FALSE;
1767 }
1768 else
1769 {
1770 # format log entry
1771 $ErrorAbbrevs = array(
1772 self::LOGLVL_FATAL => "FTL",
1773 self::LOGLVL_ERROR => "ERR",
1774 self::LOGLVL_WARNING => "WRN",
1775 self::LOGLVL_INFO => "INF",
1776 self::LOGLVL_DEBUG => "DBG",
1777 self::LOGLVL_TRACE => "TRC",
1778 );
1779 $LogEntry = date("Y-m-d H:i:s")
1780 ." ".($this->RunningInBackground ? "B" : "F")
1781 ." ".$ErrorAbbrevs[$Level]
1782 ." ".$Msg;
1783
1784 # write entry to log
1785 $Success = fwrite($FHndl, $LogEntry."\n");
1786
1787 # close log file
1788 fclose($FHndl);
1789
1790 # report to caller whether message was logged
1791 return ($Success === FALSE) ? FALSE : TRUE;
1792 }
1793 }
1794 else
1795 {
1796 # report to caller that message was not logged
1797 return FALSE;
1798 }
1799 }
1800
1822 function LoggingLevel($NewValue = DB_NOVALUE)
1823 {
1824 # constrain new level (if supplied) to within legal bounds
1825 if ($NewValue !== DB_NOVALUE)
1826 {
1827 $NewValue = max(min($NewValue, 6), 1);
1828 }
1829
1830 # set new logging level (if supplied) and return current level to caller
1831 return $this->UpdateSetting("LoggingLevel", $NewValue);
1832 }
1833
1840 function LogFile($NewValue = NULL)
1841 {
1842 if ($NewValue !== NULL) { $this->LogFileName = $NewValue; }
1843 return $this->LogFileName;
1844 }
1845
1850 const LOGLVL_TRACE = 6;
1855 const LOGLVL_DEBUG = 5;
1861 const LOGLVL_INFO = 4;
1866 const LOGLVL_WARNING = 3;
1872 const LOGLVL_ERROR = 2;
1877 const LOGLVL_FATAL = 1;
1878
1879 /*@)*/ /* Logging */
1880
1881
1882 # ---- Event Handling ----------------------------------------------------
1883 /*@(*/
1885
1889 const EVENTTYPE_DEFAULT = 1;
1895 const EVENTTYPE_CHAIN = 2;
1901 const EVENTTYPE_FIRST = 3;
1909 const EVENTTYPE_NAMED = 4;
1910
1912 const ORDER_FIRST = 1;
1914 const ORDER_MIDDLE = 2;
1916 const ORDER_LAST = 3;
1917
1926 function RegisterEvent($EventsOrEventName, $EventType = NULL)
1927 {
1928 # convert parameters to array if not already in that form
1929 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1930 : array($EventsOrEventName => $EventType);
1931
1932 # for each event
1933 foreach ($Events as $Name => $Type)
1934 {
1935 # store event information
1936 $this->RegisteredEvents[$Name]["Type"] = $Type;
1937 $this->RegisteredEvents[$Name]["Hooks"] = array();
1938 }
1939 }
1940
1947 function IsRegisteredEvent($EventName)
1948 {
1949 return array_key_exists($EventName, $this->RegisteredEvents)
1950 ? TRUE : FALSE;
1951 }
1952
1959 function IsHookedEvent($EventName)
1960 {
1961 # the event isn't hooked to if it isn't even registered
1962 if (!$this->IsRegisteredEvent($EventName))
1963 {
1964 return FALSE;
1965 }
1966
1967 # return TRUE if there is at least one callback hooked to the event
1968 return count($this->RegisteredEvents[$EventName]["Hooks"]) > 0;
1969 }
1970
1984 function HookEvent($EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
1985 {
1986 # convert parameters to array if not already in that form
1987 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1988 : array($EventsOrEventName => $Callback);
1989
1990 # for each event
1991 $Success = TRUE;
1992 foreach ($Events as $EventName => $EventCallback)
1993 {
1994 # if callback is valid
1995 if (is_callable($EventCallback))
1996 {
1997 # if this is a periodic event we process internally
1998 if (isset($this->PeriodicEvents[$EventName]))
1999 {
2000 # process event now
2001 $this->ProcessPeriodicEvent($EventName, $EventCallback);
2002 }
2003 # if specified event has been registered
2004 elseif (isset($this->RegisteredEvents[$EventName]))
2005 {
2006 # add callback for event
2007 $this->RegisteredEvents[$EventName]["Hooks"][]
2008 = array("Callback" => $EventCallback, "Order" => $Order);
2009
2010 # sort callbacks by order
2011 if (count($this->RegisteredEvents[$EventName]["Hooks"]) > 1)
2012 {
2013 usort($this->RegisteredEvents[$EventName]["Hooks"],
2014 array("ApplicationFramework", "HookEvent_OrderCompare"));
2015 }
2016 }
2017 else
2018 {
2019 $Success = FALSE;
2020 }
2021 }
2022 else
2023 {
2024 $Success = FALSE;
2025 }
2026 }
2027
2028 # report to caller whether all callbacks were hooked
2029 return $Success;
2030 }
2032 private static function HookEvent_OrderCompare($A, $B)
2033 {
2034 if ($A["Order"] == $B["Order"]) { return 0; }
2035 return ($A["Order"] < $B["Order"]) ? -1 : 1;
2036 }
2037
2048 function SignalEvent($EventName, $Parameters = NULL)
2049 {
2050 $ReturnValue = NULL;
2051
2052 # if event has been registered
2053 if (isset($this->RegisteredEvents[$EventName]))
2054 {
2055 # set up default return value (if not NULL)
2056 switch ($this->RegisteredEvents[$EventName]["Type"])
2057 {
2058 case self::EVENTTYPE_CHAIN:
2059 $ReturnValue = $Parameters;
2060 break;
2061
2062 case self::EVENTTYPE_NAMED:
2063 $ReturnValue = array();
2064 break;
2065 }
2066
2067 # for each callback for this event
2068 foreach ($this->RegisteredEvents[$EventName]["Hooks"] as $Hook)
2069 {
2070 # invoke callback
2071 $Callback = $Hook["Callback"];
2072 $Result = ($Parameters !== NULL)
2073 ? call_user_func_array($Callback, $Parameters)
2074 : call_user_func($Callback);
2075
2076 # process return value based on event type
2077 switch ($this->RegisteredEvents[$EventName]["Type"])
2078 {
2079 case self::EVENTTYPE_CHAIN:
2080 if ($Result !== NULL)
2081 {
2082 foreach ($Parameters as $Index => $Value)
2083 {
2084 if (array_key_exists($Index, $Result))
2085 {
2086 $Parameters[$Index] = $Result[$Index];
2087 }
2088 }
2089 $ReturnValue = $Parameters;
2090 }
2091 break;
2092
2093 case self::EVENTTYPE_FIRST:
2094 if ($Result !== NULL)
2095 {
2096 $ReturnValue = $Result;
2097 break 2;
2098 }
2099 break;
2100
2101 case self::EVENTTYPE_NAMED:
2102 $CallbackName = is_array($Callback)
2103 ? (is_object($Callback[0])
2104 ? get_class($Callback[0])
2105 : $Callback[0])."::".$Callback[1]
2106 : $Callback;
2107 $ReturnValue[$CallbackName] = $Result;
2108 break;
2109
2110 default:
2111 break;
2112 }
2113 }
2114 }
2115 else
2116 {
2117 $this->LogError(self::LOGLVL_WARNING,
2118 "Unregistered event signaled (".$EventName.").");
2119 }
2120
2121 # return value if any to caller
2122 return $ReturnValue;
2123 }
2124
2130 function IsStaticOnlyEvent($EventName)
2131 {
2132 return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
2133 }
2134
2145 function EventWillNextRunAt($EventName, $Callback)
2146 {
2147 # if event is not a periodic event report failure to caller
2148 if (!array_key_exists($EventName, $this->EventPeriods)) { return FALSE; }
2149
2150 # retrieve last execution time for event if available
2151 $Signature = self::GetCallbackSignature($Callback);
2152 $LastRunTime = $this->DB->Query("SELECT LastRunAt FROM PeriodicEvents"
2153 ." WHERE Signature = '".addslashes($Signature)."'", "LastRunAt");
2154
2155 # if event was not found report failure to caller
2156 if ($LastRunTime === NULL) { return FALSE; }
2157
2158 # calculate next run time based on event period
2159 $NextRunTime = strtotime($LastRunTime) + $this->EventPeriods[$EventName];
2160
2161 # report next run time to caller
2162 return $NextRunTime;
2163 }
2164
2180 function GetKnownPeriodicEvents()
2181 {
2182 # retrieve last execution times
2183 $this->DB->Query("SELECT * FROM PeriodicEvents");
2184 $LastRunTimes = $this->DB->FetchColumn("LastRunAt", "Signature");
2185
2186 # for each known event
2187 $Events = array();
2188 foreach ($this->KnownPeriodicEvents as $Signature => $Info)
2189 {
2190 # if last run time for event is available
2191 if (array_key_exists($Signature, $LastRunTimes))
2192 {
2193 # calculate next run time for event
2194 $LastRun = strtotime($LastRunTimes[$Signature]);
2195 $NextRun = $LastRun + $this->EventPeriods[$Info["Period"]];
2196 if ($Info["Period"] == "EVENT_PERIODIC") { $LastRun = FALSE; }
2197 }
2198 else
2199 {
2200 # set info to indicate run times are not known
2201 $LastRun = FALSE;
2202 $NextRun = FALSE;
2203 }
2204
2205 # add event info to list
2206 $Events[$Signature] = $Info;
2207 $Events[$Signature]["LastRun"] = $LastRun;
2208 $Events[$Signature]["NextRun"] = $NextRun;
2209 $Events[$Signature]["Parameters"] = NULL;
2210 }
2211
2212 # return list of known events to caller
2213 return $Events;
2214 }
2215
2216 /*@)*/ /* Event Handling */
2217
2218
2219 # ---- Task Management ---------------------------------------------------
2220 /*@(*/
2222
2224 const PRIORITY_HIGH = 1;
2226 const PRIORITY_MEDIUM = 2;
2228 const PRIORITY_LOW = 3;
2230 const PRIORITY_BACKGROUND = 4;
2231
2244 function QueueTask($Callback, $Parameters = NULL,
2245 $Priority = self::PRIORITY_LOW, $Description = "")
2246 {
2247 # pack task info and write to database
2248 if ($Parameters === NULL) { $Parameters = array(); }
2249 $this->DB->Query("INSERT INTO TaskQueue"
2250 ." (Callback, Parameters, Priority, Description)"
2251 ." VALUES ('".addslashes(serialize($Callback))."', '"
2252 .addslashes(serialize($Parameters))."', ".intval($Priority).", '"
2253 .addslashes($Description)."')");
2254 }
2255
2273 function QueueUniqueTask($Callback, $Parameters = NULL,
2274 $Priority = self::PRIORITY_LOW, $Description = "")
2275 {
2276 if ($this->TaskIsInQueue($Callback, $Parameters))
2277 {
2278 $QueryResult = $this->DB->Query("SELECT TaskId,Priority FROM TaskQueue"
2279 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2280 .($Parameters ? " AND Parameters = '"
2281 .addslashes(serialize($Parameters))."'" : ""));
2282 if ($QueryResult !== FALSE)
2283 {
2284 $Record = $this->DB->FetchRow();
2285 if ($Record["Priority"] > $Priority)
2286 {
2287 $this->DB->Query("UPDATE TaskQueue"
2288 ." SET Priority = ".intval($Priority)
2289 ." WHERE TaskId = ".intval($Record["TaskId"]));
2290 }
2291 }
2292 return FALSE;
2293 }
2294 else
2295 {
2296 $this->QueueTask($Callback, $Parameters, $Priority, $Description);
2297 return TRUE;
2298 }
2299 }
2300
2310 function TaskIsInQueue($Callback, $Parameters = NULL)
2311 {
2312 $QueuedCount = $this->DB->Query(
2313 "SELECT COUNT(*) AS FoundCount FROM TaskQueue"
2314 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2315 .($Parameters ? " AND Parameters = '"
2316 .addslashes(serialize($Parameters))."'" : ""),
2317 "FoundCount");
2318 $RunningCount = $this->DB->Query(
2319 "SELECT COUNT(*) AS FoundCount FROM RunningTasks"
2320 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
2321 .($Parameters ? " AND Parameters = '"
2322 .addslashes(serialize($Parameters))."'" : ""),
2323 "FoundCount");
2324 $FoundCount = $QueuedCount + $RunningCount;
2325 return ($FoundCount ? TRUE : FALSE);
2326 }
2327
2333 function GetTaskQueueSize($Priority = NULL)
2334 {
2335 return $this->GetQueuedTaskCount(NULL, NULL, $Priority);
2336 }
2337
2345 function GetQueuedTaskList($Count = 100, $Offset = 0)
2346 {
2347 return $this->GetTaskList("SELECT * FROM TaskQueue"
2348 ." ORDER BY Priority, TaskId ", $Count, $Offset);
2349 }
2350
2364 function GetQueuedTaskCount($Callback = NULL,
2365 $Parameters = NULL, $Priority = NULL, $Description = NULL)
2366 {
2367 $Query = "SELECT COUNT(*) AS TaskCount FROM TaskQueue";
2368 $Sep = " WHERE";
2369 if ($Callback !== NULL)
2370 {
2371 $Query .= $Sep." Callback = '".addslashes(serialize($Callback))."'";
2372 $Sep = " AND";
2373 }
2374 if ($Parameters !== NULL)
2375 {
2376 $Query .= $Sep." Parameters = '".addslashes(serialize($Parameters))."'";
2377 $Sep = " AND";
2378 }
2379 if ($Priority !== NULL)
2380 {
2381 $Query .= $Sep." Priority = ".intval($Priority);
2382 $Sep = " AND";
2383 }
2384 if ($Description !== NULL)
2385 {
2386 $Query .= $Sep." Description = '".addslashes($Description)."'";
2387 }
2388 return $this->DB->Query($Query, "TaskCount");
2389 }
2390
2398 function GetRunningTaskList($Count = 100, $Offset = 0)
2399 {
2400 return $this->GetTaskList("SELECT * FROM RunningTasks"
2401 ." WHERE StartedAt >= '".date("Y-m-d H:i:s",
2402 (time() - ini_get("max_execution_time")))."'"
2403 ." ORDER BY StartedAt", $Count, $Offset);
2404 }
2405
2413 function GetOrphanedTaskList($Count = 100, $Offset = 0)
2414 {
2415 return $this->GetTaskList("SELECT * FROM RunningTasks"
2416 ." WHERE StartedAt < '".date("Y-m-d H:i:s",
2417 (time() - ini_get("max_execution_time")))."'"
2418 ." ORDER BY StartedAt", $Count, $Offset);
2419 }
2420
2425 function GetOrphanedTaskCount()
2426 {
2427 return $this->DB->Query("SELECT COUNT(*) AS Count FROM RunningTasks"
2428 ." WHERE StartedAt < '".date("Y-m-d H:i:s",
2429 (time() - ini_get("max_execution_time")))."'",
2430 "Count");
2431 }
2432
2438 function ReQueueOrphanedTask($TaskId, $NewPriority = NULL)
2439 {
2440 $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
2441 $this->DB->Query("INSERT INTO TaskQueue"
2442 ." (Callback,Parameters,Priority,Description) "
2443 ."SELECT Callback, Parameters, Priority, Description"
2444 ." FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2445 if ($NewPriority !== NULL)
2446 {
2447 $NewTaskId = $this->DB->LastInsertId();
2448 $this->DB->Query("UPDATE TaskQueue SET Priority = "
2449 .intval($NewPriority)
2450 ." WHERE TaskId = ".intval($NewTaskId));
2451 }
2452 $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2453 $this->DB->Query("UNLOCK TABLES");
2454 }
2455
2460 function DeleteTask($TaskId)
2461 {
2462 $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2463 $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2464 }
2465
2473 function GetTask($TaskId)
2474 {
2475 # assume task will not be found
2476 $Task = NULL;
2477
2478 # look for task in task queue
2479 $this->DB->Query("SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2480
2481 # if task was not found in queue
2482 if (!$this->DB->NumRowsSelected())
2483 {
2484 # look for task in running task list
2485 $this->DB->Query("SELECT * FROM RunningTasks WHERE TaskId = "
2486 .intval($TaskId));
2487 }
2488
2489 # if task was found
2490 if ($this->DB->NumRowsSelected())
2491 {
2492 # if task was periodic
2493 $Row = $this->DB->FetchRow();
2494 if ($Row["Callback"] ==
2495 serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
2496 {
2497 # unpack periodic task callback
2498 $WrappedCallback = unserialize($Row["Parameters"]);
2499 $Task["Callback"] = $WrappedCallback[1];
2500 $Task["Parameters"] = $WrappedCallback[2];
2501 }
2502 else
2503 {
2504 # unpack task callback and parameters
2505 $Task["Callback"] = unserialize($Row["Callback"]);
2506 $Task["Parameters"] = unserialize($Row["Parameters"]);
2507 }
2508 }
2509
2510 # return task to caller
2511 return $Task;
2512 }
2513
2521 function TaskExecutionEnabled($NewValue = DB_NOVALUE)
2522 {
2523 return $this->UpdateSetting("TaskExecutionEnabled", $NewValue);
2524 }
2525
2531 function MaxTasks($NewValue = DB_NOVALUE)
2532 {
2533 return $this->UpdateSetting("MaxTasksRunning", $NewValue);
2534 }
2535
2543 static function GetTaskCallbackSynopsis($TaskInfo)
2544 {
2545 # if task callback is function use function name
2546 $Callback = $TaskInfo["Callback"];
2547 $Name = "";
2548 if (!is_array($Callback))
2549 {
2550 $Name = $Callback;
2551 }
2552 else
2553 {
2554 # if task callback is object
2555 if (is_object($Callback[0]))
2556 {
2557 # if task callback is encapsulated ask encapsulation for name
2558 if (method_exists($Callback[0], "GetCallbackAsText"))
2559 {
2560 $Name = $Callback[0]->GetCallbackAsText();
2561 }
2562 # else assemble name from object
2563 else
2564 {
2565 $Name = get_class($Callback[0]) . "::" . $Callback[1];
2566 }
2567 }
2568 # else assemble name from supplied info
2569 else
2570 {
2571 $Name= $Callback[0] . "::" . $Callback[1];
2572 }
2573 }
2574
2575 # if parameter array was supplied
2576 $Parameters = $TaskInfo["Parameters"];
2577 $ParameterString = "";
2578 if (is_array($Parameters))
2579 {
2580 # assemble parameter string
2581 $Separator = "";
2582 foreach ($Parameters as $Parameter)
2583 {
2584 $ParameterString .= $Separator;
2585 if (is_int($Parameter) || is_float($Parameter))
2586 {
2587 $ParameterString .= $Parameter;
2588 }
2589 else if (is_string($Parameter))
2590 {
2591 $ParameterString .= "\"".htmlspecialchars($Parameter)."\"";
2592 }
2593 else if (is_array($Parameter))
2594 {
2595 $ParameterString .= "ARRAY";
2596 }
2597 else if (is_object($Parameter))
2598 {
2599 $ParameterString .= "OBJECT";
2600 }
2601 else if (is_null($Parameter))
2602 {
2603 $ParameterString .= "NULL";
2604 }
2605 else if (is_bool($Parameter))
2606 {
2607 $ParameterString .= $Parameter ? "TRUE" : "FALSE";
2608 }
2609 else if (is_resource($Parameter))
2610 {
2611 $ParameterString .= get_resource_type($Parameter);
2612 }
2613 else
2614 {
2615 $ParameterString .= "????";
2616 }
2617 $Separator = ", ";
2618 }
2619 }
2620
2621 # assemble name and parameters and return result to caller
2622 return $Name."(".$ParameterString.")";
2623 }
2624
2625 /*@)*/ /* Task Management */
2626
2627
2628 # ---- Clean URL Support -------------------------------------------------
2629 /*@(*/
2631
2658 function AddCleanUrl($Pattern, $Page, $GetVars = NULL, $Template = NULL)
2659 {
2660 # save clean URL mapping parameters
2661 $this->CleanUrlMappings[] = array(
2662 "Pattern" => $Pattern,
2663 "Page" => $Page,
2664 "GetVars" => $GetVars,
2665 );
2666
2667 # if replacement template specified
2668 if ($Template !== NULL)
2669 {
2670 # if GET parameters specified
2671 if (count($GetVars))
2672 {
2673 # retrieve all possible permutations of GET parameters
2674 $GetPerms = $this->ArrayPermutations(array_keys($GetVars));
2675
2676 # for each permutation of GET parameters
2677 foreach ($GetPerms as $VarPermutation)
2678 {
2679 # construct search pattern for permutation
2680 $SearchPattern = "/href=([\"'])index\\.php\\?P=".$Page;
2681 $GetVarSegment = "";
2682 foreach ($VarPermutation as $GetVar)
2683 {
2684 if (preg_match("%\\\$[0-9]+%", $GetVars[$GetVar]))
2685 {
2686 $GetVarSegment .= "&amp;".$GetVar."=((?:(?!\\1)[^&])+)";
2687 }
2688 else
2689 {
2690 $GetVarSegment .= "&amp;".$GetVar."=".$GetVars[$GetVar];
2691 }
2692 }
2693 $SearchPattern .= $GetVarSegment."\\1/i";
2694
2695 # if template is actually a callback
2696 if (is_callable($Template))
2697 {
2698 # add pattern to HTML output mod callbacks list
2699 $this->OutputModificationCallbacks[] = array(
2700 "Pattern" => $Pattern,
2701 "Page" => $Page,
2702 "SearchPattern" => $SearchPattern,
2703 "Callback" => $Template,
2704 );
2705 }
2706 else
2707 {
2708 # construct replacement string for permutation
2709 $Replacement = $Template;
2710 $Index = 2;
2711 foreach ($VarPermutation as $GetVar)
2712 {
2713 $Replacement = str_replace(
2714 "\$".$GetVar, "\$".$Index, $Replacement);
2715 $Index++;
2716 }
2717 $Replacement = "href=\"".$Replacement."\"";
2718
2719 # add pattern to HTML output modifications list
2720 $this->OutputModificationPatterns[] = $SearchPattern;
2721 $this->OutputModificationReplacements[] = $Replacement;
2722 }
2723 }
2724 }
2725 else
2726 {
2727 # construct search pattern
2728 $SearchPattern = "/href=\"index\\.php\\?P=".$Page."\"/i";
2729
2730 # if template is actually a callback
2731 if (is_callable($Template))
2732 {
2733 # add pattern to HTML output mod callbacks list
2734 $this->OutputModificationCallbacks[] = array(
2735 "Pattern" => $Pattern,
2736 "Page" => $Page,
2737 "SearchPattern" => $SearchPattern,
2738 "Callback" => $Template,
2739 );
2740 }
2741 else
2742 {
2743 # add simple pattern to HTML output modifications list
2744 $this->OutputModificationPatterns[] = $SearchPattern;
2745 $this->OutputModificationReplacements[] = "href=\"".$Template."\"";
2746 }
2747 }
2748 }
2749 }
2750
2756 function CleanUrlIsMapped($Path)
2757 {
2758 foreach ($this->CleanUrlMappings as $Info)
2759 {
2760 if (preg_match($Info["Pattern"], $Path))
2761 {
2762 return TRUE;
2763 }
2764 }
2765 return FALSE;
2766 }
2767
2777 function GetCleanUrlForPath($Path)
2778 {
2779 # the search patterns and callbacks require a specific format
2780 $Format = "href=\"".str_replace("&", "&amp;", $Path)."\"";
2781 $Search = $Format;
2782
2783 # perform any regular expression replacements on the search string
2784 $Search = preg_replace(
2785 $this->OutputModificationPatterns,
2786 $this->OutputModificationReplacements,
2787 $Search);
2788
2789 # only run the callbacks if a replacement hasn't already been performed
2790 if ($Search == $Format)
2791 {
2792 # perform any callback replacements on the search string
2793 foreach ($this->OutputModificationCallbacks as $Info)
2794 {
2795 # make the information available to the callback
2796 $this->OutputModificationCallbackInfo = $Info;
2797
2798 # execute the callback
2799 $Search = preg_replace_callback(
2800 $Info["SearchPattern"],
2801 array($this, "OutputModificationCallbackShell"),
2802 $Search);
2803 }
2804 }
2805
2806 # return the path untouched if no replacements were performed
2807 if ($Search == $Format)
2808 {
2809 return $Path;
2810 }
2811
2812 # remove the bits added to the search string to get it recognized by
2813 # the replacement expressions and callbacks
2814 $Result = substr($Search, 6, -1);
2815
2816 return $Result;
2817 }
2818
2825 function GetUncleanUrlForPath($Path)
2826 {
2827 # for each clean URL mapping
2828 foreach ($this->CleanUrlMappings as $Info)
2829 {
2830 # if current path matches the clean URL pattern
2831 if (preg_match($Info["Pattern"], $Path, $Matches))
2832 {
2833 # the GET parameters for the URL, starting with the page name
2834 $GetVars = array("P" => $Info["Page"]);
2835
2836 # if additional $_GET variables specified for clean URL
2837 if ($Info["GetVars"] !== NULL)
2838 {
2839 # for each $_GET variable specified for clean URL
2840 foreach ($Info["GetVars"] as $VarName => $VarTemplate)
2841 {
2842 # start with template for variable value
2843 $Value = $VarTemplate;
2844
2845 # for each subpattern matched in current URL
2846 foreach ($Matches as $Index => $Match)
2847 {
2848 # if not first (whole) match
2849 if ($Index > 0)
2850 {
2851 # make any substitutions in template
2852 $Value = str_replace("$".$Index, $Match, $Value);
2853 }
2854 }
2855
2856 # add the GET variable
2857 $GetVars[$VarName] = $Value;
2858 }
2859 }
2860
2861 # return the unclean URL
2862 return "index.php?" . http_build_query($GetVars);
2863 }
2864 }
2865
2866 # return the path unchanged
2867 return $Path;
2868 }
2869
2875 function GetCleanUrl()
2876 {
2877 return $this->GetCleanUrlForPath($this->GetUncleanUrl());
2878 }
2879
2884 function GetUncleanUrl()
2885 {
2886 $GetVars = array("P" => $this->GetPageName()) + $_GET;
2887 return "index.php?" . http_build_query($GetVars);
2888 }
2889
2890 /*@)*/ /* Clean URL Support */
2891
2892
2893 # ---- Server Environment ------------------------------------------------
2894 /*@(*/
2896
2902 static function SessionLifetime($NewValue = NULL)
2903 {
2904 if ($NewValue !== NULL)
2905 {
2906 self::$SessionLifetime = $NewValue;
2907 }
2908 return self::$SessionLifetime;
2909 }
2910
2916 static function HtaccessSupport()
2917 {
2918 # HTACCESS_SUPPORT is set in the .htaccess file
2919 return isset($_SERVER["HTACCESS_SUPPORT"]);
2920 }
2921
2929 static function RootUrl()
2930 {
2931 # return override value if one is set
2932 if (self::$RootUrlOverride !== NULL)
2933 {
2934 return self::$RootUrlOverride;
2935 }
2936
2937 # determine scheme name
2938 $Protocol = (isset($_SERVER["HTTPS"]) ? "https" : "http");
2939
2940 # if HTTP_HOST is preferred or SERVER_NAME points to localhost
2941 # and HTTP_HOST is set
2942 if ((self::$PreferHttpHost || ($_SERVER["SERVER_NAME"] == "127.0.0.1"))
2943 && isset($_SERVER["HTTP_HOST"]))
2944 {
2945 # use HTTP_HOST for domain name
2946 $DomainName = $_SERVER["HTTP_HOST"];
2947 }
2948 else
2949 {
2950 # use SERVER_NAME for domain name
2951 $DomainName = $_SERVER["HTTP_HOST"];
2952 }
2953
2954 # build URL root and return to caller
2955 return $Protocol."://".$DomainName;
2956 }
2957
2972 static function RootUrlOverride($NewValue = self::NOVALUE)
2973 {
2974 if ($NewValue !== self::NOVALUE)
2975 {
2976 self::$RootUrlOverride = strlen(trim($NewValue)) ? $NewValue : NULL;
2977 }
2978 return self::$RootUrlOverride;
2979 }
2980
2990 static function BaseUrl()
2991 {
2992 $BaseUrl = self::RootUrl().dirname($_SERVER["SCRIPT_NAME"]);
2993 if (substr($BaseUrl, -1) != "/") { $BaseUrl .= "/"; }
2994 return $BaseUrl;
2995 }
2996
3004 static function FullUrl()
3005 {
3006 return self::RootUrl().$_SERVER["REQUEST_URI"];
3007 }
3008
3019 static function PreferHttpHost($NewValue = NULL)
3020 {
3021 if ($NewValue !== NULL)
3022 {
3023 self::$PreferHttpHost = ($NewValue ? TRUE : FALSE);
3024 }
3025 return self::$PreferHttpHost;
3026 }
3027
3032 static function BasePath()
3033 {
3034 $BasePath = dirname($_SERVER["SCRIPT_NAME"]);
3035
3036 if (substr($BasePath, -1) != "/")
3037 {
3038 $BasePath .= "/";
3039 }
3040
3041 return $BasePath;
3042 }
3043
3049 static function GetScriptUrl()
3050 {
3051 if (array_key_exists("SCRIPT_URL", $_SERVER))
3052 {
3053 return $_SERVER["SCRIPT_URL"];
3054 }
3055 elseif (array_key_exists("REDIRECT_URL", $_SERVER))
3056 {
3057 return $_SERVER["REDIRECT_URL"];
3058 }
3059 elseif (array_key_exists("REQUEST_URI", $_SERVER))
3060 {
3061 $Pieces = parse_url($_SERVER["REQUEST_URI"]);
3062 return $Pieces["path"];
3063 }
3064 else
3065 {
3066 return NULL;
3067 }
3068 }
3069
3078 static function WasUrlRewritten($ScriptName="index.php")
3079 {
3080 # needed to get the path of the URL minus the query and fragment pieces
3081 $Components = parse_url(self::GetScriptUrl());
3082
3083 # if parsing was successful and a path is set
3084 if (is_array($Components) && isset($Components["path"]))
3085 {
3086 $BasePath = self::BasePath();
3087 $Path = $Components["path"];
3088
3089 # the URL was rewritten if the path isn't the base path, i.e., the
3090 # home page, and the file in the URL isn't the script generating the
3091 # page
3092 if ($BasePath != $Path && basename($Path) != $ScriptName)
3093 {
3094 return TRUE;
3095 }
3096 }
3097
3098 # the URL wasn't rewritten
3099 return FALSE;
3100 }
3101
3107 static function GetFreeMemory()
3108 {
3109 return self::GetPhpMemoryLimit() - memory_get_usage();
3110 }
3111
3117 static function GetPhpMemoryLimit()
3118 {
3119 $Str = strtoupper(ini_get("memory_limit"));
3120 if (substr($Str, -1) == "B") { $Str = substr($Str, 0, strlen($Str) - 1); }
3121 switch (substr($Str, -1))
3122 {
3123 case "K": $MemoryLimit = (int)$Str * 1024; break;
3124 case "M": $MemoryLimit = (int)$Str * 1048576; break;
3125 case "G": $MemoryLimit = (int)$Str * 1073741824; break;
3126 default: $MemoryLimit = (int)$Str; break;
3127 }
3128 return $MemoryLimit;
3129 }
3130
3138 function MaxExecutionTime($NewValue = NULL)
3139 {
3140 if (func_num_args() && !ini_get("safe_mode"))
3141 {
3142 if ($NewValue != $this->Settings["MaxExecTime"])
3143 {
3144 $this->Settings["MaxExecTime"] = max($NewValue, 5);
3145 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
3146 ." SET MaxExecTime = '"
3147 .intval($this->Settings["MaxExecTime"])."'");
3148 }
3149 ini_set("max_execution_time", $this->Settings["MaxExecTime"]);
3150 set_time_limit($this->Settings["MaxExecTime"]);
3151 }
3152 return ini_get("max_execution_time");
3153 }
3154
3155 /*@)*/ /* Server Environment */
3156
3157
3158 # ---- Backward Compatibility --------------------------------------------
3159 /*@(*/
3161
3166 function FindCommonTemplate($BaseName)
3167 {
3168 return $this->FindFile(
3169 $this->IncludeDirList, $BaseName, array("tpl", "html"));
3170 }
3171
3172 /*@)*/ /* Backward Compatibility */
3173
3174
3175 # ---- PRIVATE INTERFACE -------------------------------------------------
3176
3177 private $AdditionalRequiredUIFiles = array();
3178 private $BackgroundTaskMemLeakLogThreshold = 10; # percentage of max mem
3179 private $BackgroundTaskMinFreeMemPercent = 25;
3180 private $BrowserDetectFunc;
3181 private $CacheCurrentPage = TRUE;
3182 private $CleanUrlMappings = array();
3183 private $CleanUrlRewritePerformed = FALSE;
3184 private $CssUrlFingerprintPath;
3185 private $DB;
3186 private $DefaultPage = "Home";
3187 private $DoNotMinimizeList = array();
3188 private $EnvIncludes = array();
3189 private $ExecutionStartTime;
3190 private $FoundUIFiles = array();
3191 private $HtmlCharset = "UTF-8";
3192 private $JSMinimizerJavaScriptPackerAvailable = FALSE;
3193 private $JSMinimizerJShrinkAvailable = TRUE;
3194 private $JumpToPage = NULL;
3195 private $LogFileName = "local/logs/site.log";
3196 private $MaxRunningTasksToTrack = 250;
3197 private $OutputModificationCallbackInfo;
3198 private $OutputModificationCallbacks = array();
3199 private $OutputModificationPatterns = array();
3200 private $OutputModificationReplacements = array();
3201 private $PageCacheTags = array();
3202 private $PageName;
3203 private $PostProcessingFuncs = array();
3204 private $RunningInBackground = FALSE;
3205 private $RunningTask;
3206 private $SavedContext;
3207 private $SaveTemplateLocationCache = FALSE;
3208 private $SessionStorage;
3209 private $SessionGcProbability;
3210 private $Settings;
3211 private $SuppressHTML = FALSE;
3212 private $TemplateLocationCache;
3213 private $TemplateLocationCacheInterval = 60; # in minutes
3214 private $TemplateLocationCacheExpiration;
3215 private $UnbufferedCallbacks = array();
3216 private $UrlFingerprintBlacklist = array();
3217 private $UseBaseTag = FALSE;
3218
3219 private static $ActiveUI = "default";
3220 private static $AppName = "ScoutAF";
3221 private static $JSMinCacheDir = "local/data/caches/JSMin";
3222 private static $ObjectDirectories = array();
3223 private static $ObjectLocationCache;
3224 private static $ObjectLocationCacheInterval = 60;
3225 private static $ObjectLocationCacheExpiration;
3226 private static $PreferHttpHost = FALSE;
3227 private static $RootUrlOverride = NULL;
3228 private static $SaveObjectLocationCache = FALSE;
3229 private static $ScssCacheDir = "local/data/caches/SCSS";
3230 private static $SessionLifetime = 1440; # in seconds
3231
3232 # offset used to generate page cache tag IDs from numeric tags
3233 const PAGECACHETAGIDOFFSET = 100000;
3234
3239 private $NoTSR = FALSE;
3240
3241 private $KnownPeriodicEvents = array();
3242 private $PeriodicEvents = array(
3243 "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
3244 "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
3245 "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
3246 "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
3247 "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
3248 );
3249 private $EventPeriods = array(
3250 "EVENT_HOURLY" => 3600,
3251 "EVENT_DAILY" => 86400,
3252 "EVENT_WEEKLY" => 604800,
3253 "EVENT_MONTHLY" => 2592000,
3254 "EVENT_PERIODIC" => 0,
3255 );
3256 private $UIEvents = array(
3257 "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
3258 "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
3259 "EVENT_PHP_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
3260 "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
3261 "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
3262 "EVENT_PAGE_OUTPUT_FILTER" => self::EVENTTYPE_CHAIN,
3263 );
3264
3269 private function LoadSettings()
3270 {
3271 # read settings in from database
3272 $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
3273 $this->Settings = $this->DB->FetchRow();
3274
3275 # if settings were not previously initialized
3276 if ($this->Settings === FALSE)
3277 {
3278 # initialize settings in database
3279 $this->DB->Query("INSERT INTO ApplicationFrameworkSettings"
3280 ." (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
3281
3282 # read new settings in from database
3283 $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
3284 $this->Settings = $this->DB->FetchRow();
3285
3286 # bail out if reloading new settings failed
3287 if ($this->Settings === FALSE)
3288 {
3289 throw new Exception(
3290 "Unable to load application framework settings.");
3291 }
3292 }
3293
3294 # if base path was not previously set or we appear to have moved
3295 if (!array_key_exists("BasePath", $this->Settings)
3296 || (!strlen($this->Settings["BasePath"]))
3297 || (!array_key_exists("BasePathCheck", $this->Settings))
3298 || (__FILE__ != $this->Settings["BasePathCheck"]))
3299 {
3300 # attempt to extract base path from Apache .htaccess file
3301 if (is_readable(".htaccess"))
3302 {
3303 $Lines = file(".htaccess");
3304 foreach ($Lines as $Line)
3305 {
3306 if (preg_match("/\\s*RewriteBase\\s+/", $Line))
3307 {
3308 $Pieces = preg_split(
3309 "/\\s+/", $Line, NULL, PREG_SPLIT_NO_EMPTY);
3310 $BasePath = $Pieces[1];
3311 }
3312 }
3313 }
3314
3315 # if base path was found
3316 if (isset($BasePath))
3317 {
3318 # save base path locally
3319 $this->Settings["BasePath"] = $BasePath;
3320
3321 # save base path to database
3322 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
3323 ." SET BasePath = '".addslashes($BasePath)."'"
3324 .", BasePathCheck = '".addslashes(__FILE__)."'");
3325 }
3326 }
3327
3328 # retrieve template location cache
3329 $this->TemplateLocationCache = unserialize(
3330 $this->Settings["TemplateLocationCache"]);
3331 $this->TemplateLocationCacheInterval =
3332 $this->Settings["TemplateLocationCacheInterval"];
3333 $this->TemplateLocationCacheExpiration =
3334 strtotime($this->Settings["TemplateLocationCacheExpiration"]);
3335
3336 # if template location cache looks invalid or has expired
3337 $CurrentTime = time();
3338 if (!count($this->TemplateLocationCache)
3339 || ($CurrentTime >= $this->TemplateLocationCacheExpiration))
3340 {
3341 # clear cache and reset cache expiration
3342 $this->TemplateLocationCache = array();
3343 $this->TemplateLocationCacheExpiration =
3344 $CurrentTime + ($this->TemplateLocationCacheInterval * 60);
3345 $this->SaveTemplateLocationCache = TRUE;
3346 }
3347
3348 # retrieve object location cache
3349 self::$ObjectLocationCache =
3350 unserialize($this->Settings["ObjectLocationCache"]);
3351 self::$ObjectLocationCacheInterval =
3352 $this->Settings["ObjectLocationCacheInterval"];
3353 self::$ObjectLocationCacheExpiration =
3354 strtotime($this->Settings["ObjectLocationCacheExpiration"]);
3355
3356 # if object location cache looks invalid or has expired
3357 if (!count(self::$ObjectLocationCache)
3358 || ($CurrentTime >= self::$ObjectLocationCacheExpiration))
3359 {
3360 # clear cache and reset cache expiration
3361 self::$ObjectLocationCache = array();
3362 self::$ObjectLocationCacheExpiration =
3363 $CurrentTime + (self::$ObjectLocationCacheInterval * 60);
3364 self::$SaveObjectLocationCache = TRUE;
3365 }
3366 }
3367
3374 private function RewriteCleanUrls($PageName)
3375 {
3376 # if URL rewriting is supported by the server
3377 if ($this->HtaccessSupport())
3378 {
3379 # retrieve current URL and remove base path if present
3380 $Url = $this->GetPageLocation();
3381
3382 # for each clean URL mapping
3383 foreach ($this->CleanUrlMappings as $Info)
3384 {
3385 # if current URL matches clean URL pattern
3386 if (preg_match($Info["Pattern"], $Url, $Matches))
3387 {
3388 # set new page
3389 $PageName = $Info["Page"];
3390
3391 # if $_GET variables specified for clean URL
3392 if ($Info["GetVars"] !== NULL)
3393 {
3394 # for each $_GET variable specified for clean URL
3395 foreach ($Info["GetVars"] as $VarName => $VarTemplate)
3396 {
3397 # start with template for variable value
3398 $Value = $VarTemplate;
3399
3400 # for each subpattern matched in current URL
3401 foreach ($Matches as $Index => $Match)
3402 {
3403 # if not first (whole) match
3404 if ($Index > 0)
3405 {
3406 # make any substitutions in template
3407 $Value = str_replace("$".$Index, $Match, $Value);
3408 }
3409 }
3410
3411 # set $_GET variable
3412 $_GET[$VarName] = $Value;
3413 }
3414 }
3415
3416 # set flag indicating clean URL mapped
3417 $this->CleanUrlRewritePerformed = TRUE;
3418
3419 # stop looking for a mapping
3420 break;
3421 }
3422 }
3423 }
3424
3425 # return (possibly) updated page name to caller
3426 return $PageName;
3427 }
3428
3447 private function FindFile($DirectoryList, $BaseName,
3448 $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
3449 {
3450 # generate template cache index for this page
3451 $CacheIndex = md5(serialize($DirectoryList))
3452 .":".self::$ActiveUI.":".$BaseName;
3453
3454 # if caching is enabled and we have cached location
3455 if (($this->TemplateLocationCacheInterval > 0)
3456 && array_key_exists($CacheIndex,
3457 $this->TemplateLocationCache))
3458 {
3459 # use template location from cache
3460 $FoundFileName = $this->TemplateLocationCache[$CacheIndex];
3461 }
3462 else
3463 {
3464 # if suffixes specified and base name does not include suffix
3465 if (count($PossibleSuffixes)
3466 && !preg_match("/\.[a-zA-Z0-9]+$/", $BaseName))
3467 {
3468 # add versions of file names with suffixes to file name list
3469 $FileNames = array();
3470 foreach ($PossibleSuffixes as $Suffix)
3471 {
3472 $FileNames[] = $BaseName.".".$Suffix;
3473 }
3474 }
3475 else
3476 {
3477 # use base name as file name
3478 $FileNames = array($BaseName);
3479 }
3480
3481 # if prefixes specified
3482 if (count($PossiblePrefixes))
3483 {
3484 # add versions of file names with prefixes to file name list
3485 $NewFileNames = array();
3486 foreach ($FileNames as $FileName)
3487 {
3488 foreach ($PossiblePrefixes as $Prefix)
3489 {
3490 $NewFileNames[] = $Prefix.$FileName;
3491 }
3492 }
3493 $FileNames = $NewFileNames;
3494 }
3495
3496 # for each possible location
3497 $FoundFileName = NULL;
3498 foreach ($DirectoryList as $Dir)
3499 {
3500 # substitute active UI name into path
3501 $Dir = str_replace("%ACTIVEUI%", self::$ActiveUI, $Dir);
3502
3503 # for each possible file name
3504 foreach ($FileNames as $File)
3505 {
3506 # if template is found at location
3507 if (file_exists($Dir.$File))
3508 {
3509 # save full template file name and stop looking
3510 $FoundFileName = $Dir.$File;
3511 break 2;
3512 }
3513 }
3514 }
3515
3516 # save location in cache
3517 $this->TemplateLocationCache[$CacheIndex]
3518 = $FoundFileName;
3519
3520 # set flag indicating that cache should be saved
3521 $this->SaveTemplateLocationCache = TRUE;
3522 }
3523
3524 # return full template file name to caller
3525 return $FoundFileName;
3526 }
3527
3536 private function CompileScssFile($SrcFile)
3537 {
3538 # build path to CSS file
3539 $DstFile = self::$ScssCacheDir."/".dirname($SrcFile)
3540 ."/".basename($SrcFile);
3541 $DstFile = substr_replace($DstFile, "css", -4);
3542
3543 # if SCSS file is newer than CSS file
3544 if (!file_exists($DstFile)
3545 || (filemtime($SrcFile) > filemtime($DstFile)))
3546 {
3547 # attempt to create CSS cache subdirectory if not present
3548 if (!is_dir(dirname($DstFile)))
3549 {
3550 @mkdir(dirname($DstFile), 0777, TRUE);
3551 }
3552
3553 # if CSS cache directory and CSS file path appear writable
3554 static $CacheDirIsWritable;
3555 if (!isset($CacheDirIsWritable))
3556 { $CacheDirIsWritable = is_writable(self::$ScssCacheDir); }
3557 if (is_writable($DstFile)
3558 || (!file_exists($DstFile) && $CacheDirIsWritable))
3559 {
3560 # load SCSS and compile to CSS
3561 $ScssCode = file_get_contents($SrcFile);
3562 $ScssCompiler = new scssc();
3563 $ScssCompiler->setFormatter($this->GenerateCompactCss()
3564 ? "scss_formatter_compressed" : "scss_formatter");
3565 try
3566 {
3567 $CssCode = $ScssCompiler->compile($ScssCode);
3568
3569 # add fingerprinting for URLs in CSS
3570 $this->CssUrlFingerprintPath = dirname($SrcFile);
3571 $CssCode = preg_replace_callback(
3572 "/url\((['\"]*)(.+)\.([a-z]+)(['\"]*)\)/",
3573 array($this, "CssUrlFingerprintInsertion"),
3574 $CssCode);
3575
3576 # write out CSS file
3577 file_put_contents($DstFile, $CssCode);
3578 }
3579 catch (Exception $Ex)
3580 {
3581 $this->LogError(self::LOGLVL_ERROR,
3582 "Error compiling SCSS file ".$SrcFile.": "
3583 .$Ex->getMessage());
3584 $DstFile = NULL;
3585 }
3586 }
3587 else
3588 {
3589 # log error and set CSS file path to indicate failure
3590 $this->LogError(self::LOGLVL_ERROR,
3591 "Unable to write out CSS file for SCSS file ".$SrcFile);
3592 $DstFile = NULL;
3593 }
3594 }
3595
3596 # return CSS file path to caller
3597 return $DstFile;
3598 }
3599
3607 private function MinimizeJavascriptFile($SrcFile)
3608 {
3609 # bail out if file is on exclusion list
3610 foreach ($this->DoNotMinimizeList as $DNMFile)
3611 {
3612 if (($SrcFile == $DNMFile) || (basename($SrcFile) == $DNMFile))
3613 {
3614 return NULL;
3615 }
3616 }
3617
3618 # build path to minimized file
3619 $DstFile = self::$JSMinCacheDir."/".dirname($SrcFile)
3620 ."/".basename($SrcFile);
3621 $DstFile = substr_replace($DstFile, ".min", -3, 0);
3622
3623 # if original file is newer than minimized file
3624 if (!file_exists($DstFile)
3625 || (filemtime($SrcFile) > filemtime($DstFile)))
3626 {
3627 # attempt to create cache subdirectory if not present
3628 if (!is_dir(dirname($DstFile)))
3629 {
3630 @mkdir(dirname($DstFile), 0777, TRUE);
3631 }
3632
3633 # if cache directory and minimized file path appear writable
3634 static $CacheDirIsWritable;
3635 if (!isset($CacheDirIsWritable))
3636 { $CacheDirIsWritable = is_writable(self::$JSMinCacheDir); }
3637 if (is_writable($DstFile)
3638 || (!file_exists($DstFile) && $CacheDirIsWritable))
3639 {
3640 # load JavaScript code
3641 $Code = file_get_contents($SrcFile);
3642
3643 # decide which minimizer to use
3644 if ($this->JSMinimizerJavaScriptPackerAvailable
3645 && $this->JSMinimizerJShrinkAvailable)
3646 {
3647 $Minimizer = (strlen($Code) < 5000)
3648 ? "JShrink" : "JavaScriptPacker";
3649 }
3650 elseif ($this->JSMinimizerJShrinkAvailable)
3651 {
3652 $Minimizer = "JShrink";
3653 }
3654 else
3655 {
3656 $Minimizer = "NONE";
3657 }
3658
3659 # minimize code
3660 switch ($Minimizer)
3661 {
3662 case "JavaScriptMinimizer":
3663 $Packer = new JavaScriptPacker($Code, "Normal");
3664 $MinimizedCode = $Packer->pack();
3665 break;
3666
3667 case "JShrink":
3668 try
3669 {
3670 $MinimizedCode = \JShrink\Minifier::minify($Code);
3671 }
3672 catch (Exception $Exception)
3673 {
3674 unset($MinimizedCode);
3675 $MinimizeError = $Exception->getMessage();
3676 }
3677 break;
3678 }
3679
3680 # if minimization succeeded
3681 if (isset($MinimizedCode))
3682 {
3683 # write out minimized file
3684 file_put_contents($DstFile, $MinimizedCode);
3685 }
3686 else
3687 {
3688 # log error and set destination file path to indicate failure
3689 $ErrMsg = "Unable to minimize JavaScript file ".$SrcFile;
3690 if (isset($MinimizeError))
3691 {
3692 $ErrMsg .= " (".$MinimizeError.")";
3693 }
3694 $this->LogError(self::LOGLVL_ERROR, $ErrMsg);
3695 $DstFile = NULL;
3696 }
3697 }
3698 else
3699 {
3700 # log error and set destination file path to indicate failure
3701 $this->LogError(self::LOGLVL_ERROR,
3702 "Unable to write out minimized JavaScript for file ".$SrcFile);
3703 $DstFile = NULL;
3704 }
3705 }
3706
3707 # return CSS file path to caller
3708 return $DstFile;
3709 }
3710
3718 private function CssUrlFingerprintInsertion($Matches)
3719 {
3720 # generate fingerprint string from CSS file modification time
3721 $FileName = realpath($this->CssUrlFingerprintPath."/".
3722 $Matches[2].".".$Matches[3]);
3723 $MTime = filemtime($FileName);
3724 $Fingerprint = sprintf("%06X", ($MTime % 0xFFFFFF));
3725
3726 # build URL string with fingerprint and return it to caller
3727 return "url(".$Matches[1].$Matches[2].".".$Fingerprint
3728 .".".$Matches[3].$Matches[4].")";
3729 }
3730
3737 private function GetRequiredFilesNotYetLoaded($PageContentFile)
3738 {
3739 # start out assuming no files required
3740 $RequiredFiles = array();
3741
3742 # if page content file supplied
3743 if ($PageContentFile)
3744 {
3745 # if file containing list of required files is available
3746 $Path = dirname($PageContentFile);
3747 $RequireListFile = $Path."/REQUIRES";
3748 if (file_exists($RequireListFile))
3749 {
3750 # read in list of required files
3751 $RequestedFiles = file($RequireListFile);
3752
3753 # for each line in required file list
3754 foreach ($RequestedFiles as $Line)
3755 {
3756 # if line is not a comment
3757 $Line = trim($Line);
3758 if (!preg_match("/^#/", $Line))
3759 {
3760 # if file has not already been loaded
3761 if (!in_array($Line, $this->FoundUIFiles))
3762 {
3763 # add to list of required files
3764 $RequiredFiles[] = $Line;
3765 }
3766 }
3767 }
3768 }
3769 }
3770
3771 # add in additional required files if any
3772 if (count($this->AdditionalRequiredUIFiles))
3773 {
3774 # make sure there are no duplicates
3775 $AdditionalRequiredUIFiles = array_unique(
3776 $this->AdditionalRequiredUIFiles);
3777
3778 $RequiredFiles = array_merge(
3779 $RequiredFiles, $AdditionalRequiredUIFiles);
3780 }
3781
3782 # return list of required files to caller
3783 return $RequiredFiles;
3784 }
3785
3789 private function SetUpObjectAutoloading()
3790 {
3792 function __autoload($ClassName)
3793 {
3794 // @codingStandardsIgnoreStart
3795 ApplicationFramework::AutoloadObjects($ClassName);
3796 // @codingStandardsIgnoreEnd
3797 }
3798 }
3799
3805 static function AutoloadObjects($ClassName)
3806 {
3807 # if caching is not turned off
3808 # and we have a cached location for class
3809 # and file at cached location is readable
3810 if ((self::$ObjectLocationCacheInterval > 0)
3811 && array_key_exists($ClassName,
3812 self::$ObjectLocationCache)
3813 && is_readable(self::$ObjectLocationCache[$ClassName]))
3814 {
3815 # use object location from cache
3816 require_once(self::$ObjectLocationCache[$ClassName]);
3817 }
3818 else
3819 {
3820 # convert any namespace separators in class name
3821 $ClassName = str_replace("\\", "-", $ClassName);
3822
3823 # for each possible object file directory
3824 static $FileLists;
3825 foreach (self::$ObjectDirectories as $Location => $Info)
3826 {
3827 # make any needed replacements in directory path
3828 $Location = str_replace("%ACTIVEUI%", self::$ActiveUI, $Location);
3829
3830 # if directory looks valid
3831 if (is_dir($Location))
3832 {
3833 # build class file name
3834 $NewClassName = ($Info["ClassPattern"] && $Info["ClassReplacement"])
3835 ? preg_replace($Info["ClassPattern"],
3836 $Info["ClassReplacement"], $ClassName)
3837 : $ClassName;
3838
3839 # read in directory contents if not already retrieved
3840 if (!isset($FileLists[$Location]))
3841 {
3842 $FileLists[$Location] = self::ReadDirectoryTree(
3843 $Location, '/^.+\.php$/i');
3844 }
3845
3846 # for each file in target directory
3847 $FileNames = $FileLists[$Location];
3848 $TargetName = strtolower($Info["Prefix"].$NewClassName.".php");
3849 foreach ($FileNames as $FileName)
3850 {
3851 # if file matches our target object file name
3852 if (strtolower($FileName) == $TargetName)
3853 {
3854 # include object file
3855 require_once($Location.$FileName);
3856
3857 # save location to cache
3858 self::$ObjectLocationCache[$ClassName]
3859 = $Location.$FileName;
3860
3861 # set flag indicating that cache should be saved
3862 self::$SaveObjectLocationCache = TRUE;
3863
3864 # stop looking
3865 break 2;
3866 }
3867 }
3868 }
3869 }
3870 }
3871 }
3881 private static function ReadDirectoryTree($Directory, $Pattern)
3882 {
3883 $CurrentDir = getcwd();
3884 chdir($Directory);
3885 $DirIter = new RecursiveDirectoryIterator(".");
3886 $IterIter = new RecursiveIteratorIterator($DirIter);
3887 $RegexResults = new RegexIterator($IterIter, $Pattern,
3888 RecursiveRegexIterator::GET_MATCH);
3889 $FileList = array();
3890 foreach ($RegexResults as $Result)
3891 {
3892 $FileList[] = substr($Result[0], 2);
3893 }
3894 chdir($CurrentDir);
3895 return $FileList;
3896 }
3897
3901 private function UndoMagicQuotes()
3902 {
3903 # if this PHP version has magic quotes support
3904 if (version_compare(PHP_VERSION, "5.4.0", "<"))
3905 {
3906 # turn off runtime magic quotes if on
3907 if (get_magic_quotes_runtime())
3908 {
3909 // @codingStandardsIgnoreStart
3910 set_magic_quotes_runtime(FALSE);
3911 // @codingStandardsIgnoreEnd
3912 }
3913
3914 # if magic quotes GPC is on
3915 if (get_magic_quotes_gpc())
3916 {
3917 # strip added slashes from incoming variables
3918 $GPC = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
3919 array_walk_recursive($GPC,
3920 array($this, "UndoMagicQuotes_StripCallback"));
3921 }
3922 }
3923 }
3924 private function UndoMagicQuotes_StripCallback(&$Value)
3925 {
3926 $Value = stripslashes($Value);
3927 }
3928
3933 private function LoadUIFunctions()
3934 {
3935 $Dirs = array(
3936 "local/interface/%ACTIVEUI%/include",
3937 "interface/%ACTIVEUI%/include",
3938 "local/interface/default/include",
3939 "interface/default/include",
3940 );
3941 foreach ($Dirs as $Dir)
3942 {
3943 $Dir = str_replace("%ACTIVEUI%", self::$ActiveUI, $Dir);
3944 if (is_dir($Dir))
3945 {
3946 $FileNames = scandir($Dir);
3947 foreach ($FileNames as $FileName)
3948 {
3949 if (preg_match("/^F-([A-Za-z0-9_]+)\.php/",
3950 $FileName, $Matches)
3951 || preg_match("/^F-([A-Za-z0-9_]+)\.html/",
3952 $FileName, $Matches))
3953 {
3954 if (!function_exists($Matches[1]))
3955 {
3956 include_once($Dir."/".$FileName);
3957 }
3958 }
3959 }
3960 }
3961 }
3962 }
3963
3969 private function ProcessPeriodicEvent($EventName, $Callback)
3970 {
3971 # retrieve last execution time for event if available
3972 $Signature = self::GetCallbackSignature($Callback);
3973 $LastRun = $this->DB->Query("SELECT LastRunAt FROM PeriodicEvents"
3974 ." WHERE Signature = '".addslashes($Signature)."'", "LastRunAt");
3975
3976 # determine whether enough time has passed for event to execute
3977 $ShouldExecute = (($LastRun === NULL)
3978 || (time() > (strtotime($LastRun) + $this->EventPeriods[$EventName])))
3979 ? TRUE : FALSE;
3980
3981 # if event should run
3982 if ($ShouldExecute)
3983 {
3984 # add event to task queue
3985 $WrapperCallback = array("ApplicationFramework", "PeriodicEventWrapper");
3986 $WrapperParameters = array(
3987 $EventName, $Callback, array("LastRunAt" => $LastRun));
3988 $this->QueueUniqueTask($WrapperCallback, $WrapperParameters);
3989 }
3990
3991 # add event to list of periodic events
3992 $this->KnownPeriodicEvents[$Signature] = array(
3993 "Period" => $EventName,
3994 "Callback" => $Callback,
3995 "Queued" => $ShouldExecute);
3996 }
3997
4005 private static function PeriodicEventWrapper($EventName, $Callback, $Parameters)
4006 {
4007 static $DB;
4008 if (!isset($DB)) { $DB = new Database(); }
4009
4010 # run event
4011 $ReturnVal = call_user_func_array($Callback, $Parameters);
4012
4013 # if event is already in database
4014 $Signature = self::GetCallbackSignature($Callback);
4015 if ($DB->Query("SELECT COUNT(*) AS EventCount FROM PeriodicEvents"
4016 ." WHERE Signature = '".addslashes($Signature)."'", "EventCount"))
4017 {
4018 # update last run time for event
4019 $DB->Query("UPDATE PeriodicEvents SET LastRunAt = "
4020 .(($EventName == "EVENT_PERIODIC")
4021 ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
4022 : "NOW()")
4023 ." WHERE Signature = '".addslashes($Signature)."'");
4024 }
4025 else
4026 {
4027 # add last run time for event to database
4028 $DB->Query("INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES "
4029 ."('".addslashes($Signature)."', "
4030 .(($EventName == "EVENT_PERIODIC")
4031 ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
4032 : "NOW()").")");
4033 }
4034 }
4035
4041 private static function GetCallbackSignature($Callback)
4042 {
4043 return !is_array($Callback) ? $Callback
4044 : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
4045 ."::".$Callback[1];
4046 }
4047
4052 private function PrepForTSR()
4053 {
4054 # if HTML has been output and it's time to launch another task
4055 # (only TSR if HTML has been output because otherwise browsers
4056 # may misbehave after connection is closed)
4057 if (($this->JumpToPage || !$this->SuppressHTML)
4058 && (time() > (strtotime($this->Settings["LastTaskRunAt"])
4059 + (ini_get("max_execution_time")
4060 / $this->Settings["MaxTasksRunning"]) + 5))
4061 && $this->GetTaskQueueSize()
4062 && $this->Settings["TaskExecutionEnabled"])
4063 {
4064 # begin buffering output for TSR
4065 ob_start();
4066
4067 # let caller know it is time to launch another task
4068 return TRUE;
4069 }
4070 else
4071 {
4072 # let caller know it is not time to launch another task
4073 return FALSE;
4074 }
4075 }
4076
4081 private function LaunchTSR()
4082 {
4083 # set headers to close out connection to browser
4084 if (!$this->NoTSR)
4085 {
4086 ignore_user_abort(TRUE);
4087 header("Connection: close");
4088 header("Content-Length: ".ob_get_length());
4089 }
4090
4091 # output buffered content
4092 while (ob_get_level()) { ob_end_flush(); }
4093 flush();
4094
4095 # write out any outstanding data and end HTTP session
4096 session_write_close();
4097
4098 # set flag indicating that we are now running in background
4099 $this->RunningInBackground = TRUE;
4100
4101 # handle garbage collection for session data
4102 if (isset($this->SessionStorage) &&
4103 (rand()/getrandmax()) <= $this->SessionGcProbability)
4104 {
4105 # determine when sessions will expire
4106 $ExpiredTime = strtotime("-". self::$SessionLifetime." seconds");
4107
4108 # iterate over the files in our session storage, deleting the expired
4109 # ones (sess_ is php's session prefix)
4110 foreach (scandir($this->SessionStorage) as $TgtFile)
4111 {
4112 $FullPath = $this->SessionStorage."/".$TgtFile;
4113 if ( is_file($FullPath) &&
4114 preg_match("/^sess_/", $TgtFile) &&
4115 filectime($FullPath) < $ExpiredTime)
4116 {
4117 unlink($FullPath);
4118 }
4119 }
4120 }
4121
4122 # if there is still a task in the queue
4123 if ($this->GetTaskQueueSize())
4124 {
4125 # garbage collect to give as much memory as possible for tasks
4126 if (function_exists("gc_collect_cycles")) { gc_collect_cycles(); }
4127
4128 # turn on output buffering to (hopefully) record any crash output
4129 ob_start();
4130
4131 # lock tables and grab last task run time to double check
4132 $this->DB->Query("LOCK TABLES ApplicationFrameworkSettings WRITE");
4133 $this->LoadSettings();
4134
4135 # if still time to launch another task
4136 if (time() > (strtotime($this->Settings["LastTaskRunAt"])
4137 + (ini_get("max_execution_time")
4138 / $this->Settings["MaxTasksRunning"]) + 5))
4139 {
4140 # update the "last run" time and release tables
4141 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
4142 ." SET LastTaskRunAt = '".date("Y-m-d H:i:s")."'");
4143 $this->DB->Query("UNLOCK TABLES");
4144
4145 # run tasks while there is a task in the queue
4146 # and enough time and memory left
4147 do
4148 {
4149 # run the next task
4150 $this->RunNextTask();
4151
4152 # calculate percentage of memory still available
4153 $PercentFreeMem = (self::GetFreeMemory()
4154 / self::GetPhpMemoryLimit()) * 100;
4155 }
4156 while ($this->GetTaskQueueSize()
4157 && ($this->GetSecondsBeforeTimeout() > 65)
4158 && ($PercentFreeMem > $this->BackgroundTaskMinFreeMemPercent));
4159 }
4160 else
4161 {
4162 # release tables
4163 $this->DB->Query("UNLOCK TABLES");
4164 }
4165 }
4166 }
4167
4175 private function GetTaskList($DBQuery, $Count, $Offset)
4176 {
4177 $this->DB->Query($DBQuery." LIMIT ".intval($Offset).",".intval($Count));
4178 $Tasks = array();
4179 while ($Row = $this->DB->FetchRow())
4180 {
4181 $Tasks[$Row["TaskId"]] = $Row;
4182 if ($Row["Callback"] ==
4183 serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
4184 {
4185 $WrappedCallback = unserialize($Row["Parameters"]);
4186 $Tasks[$Row["TaskId"]]["Callback"] = $WrappedCallback[1];
4187 $Tasks[$Row["TaskId"]]["Parameters"] = NULL;
4188 }
4189 else
4190 {
4191 $Tasks[$Row["TaskId"]]["Callback"] = unserialize($Row["Callback"]);
4192 $Tasks[$Row["TaskId"]]["Parameters"] = unserialize($Row["Parameters"]);
4193 }
4194 }
4195 return $Tasks;
4196 }
4197
4201 private function RunNextTask()
4202 {
4203 # lock tables to prevent same task from being run by multiple sessions
4204 $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
4205
4206 # look for task at head of queue
4207 $this->DB->Query("SELECT * FROM TaskQueue ORDER BY Priority, TaskId LIMIT 1");
4208 $Task = $this->DB->FetchRow();
4209
4210 # if there was a task available
4211 if ($Task)
4212 {
4213 # move task from queue to running tasks list
4214 $this->DB->Query("INSERT INTO RunningTasks "
4215 ."(TaskId,Callback,Parameters,Priority,Description) "
4216 ."SELECT * FROM TaskQueue WHERE TaskId = "
4217 .intval($Task["TaskId"]));
4218 $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = "
4219 .intval($Task["TaskId"]));
4220
4221 # release table locks to again allow other sessions to run tasks
4222 $this->DB->Query("UNLOCK TABLES");
4223
4224 # unpack stored task info
4225 $Callback = unserialize($Task["Callback"]);
4226 $Parameters = unserialize($Task["Parameters"]);
4227
4228 # attempt to load task callback if not already available
4229 $this->LoadFunction($Callback);
4230
4231 # save amount of free memory for later comparison
4232 $BeforeFreeMem = self::GetFreeMemory();
4233
4234 # run task
4235 $this->RunningTask = $Task;
4236 if ($Parameters)
4237 {
4238 call_user_func_array($Callback, $Parameters);
4239 }
4240 else
4241 {
4242 call_user_func($Callback);
4243 }
4244 unset($this->RunningTask);
4245
4246 # log if task leaked significant memory
4247 if (function_exists("gc_collect_cycles")) { gc_collect_cycles(); }
4248 $AfterFreeMem = self::GetFreeMemory();
4249 $LeakThreshold = self::GetPhpMemoryLimit()
4250 * ($this->BackgroundTaskMemLeakLogThreshold / 100);
4251 if (($BeforeFreeMem - $AfterFreeMem) > $LeakThreshold)
4252 {
4253 $this->LogError(self::LOGLVL_DEBUG, "Task "
4254 .self::GetTaskCallbackSynopsis(
4255 $this->GetTask($Task["TaskId"]))." leaked "
4256 .number_format($BeforeFreeMem - $AfterFreeMem)." bytes.");
4257 }
4258
4259 # remove task from running tasks list
4260 $this->DB->Query("DELETE FROM RunningTasks"
4261 ." WHERE TaskId = ".intval($Task["TaskId"]));
4262
4263 # prune running tasks list if necessary
4264 $RunningTasksCount = $this->DB->Query(
4265 "SELECT COUNT(*) AS TaskCount FROM RunningTasks", "TaskCount");
4266 if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
4267 {
4268 $this->DB->Query("DELETE FROM RunningTasks ORDER BY StartedAt"
4269 ." LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
4270 }
4271 }
4272 else
4273 {
4274 # release table locks to again allow other sessions to run tasks
4275 $this->DB->Query("UNLOCK TABLES");
4276 }
4277 }
4278
4284 function OnCrash()
4285 {
4286 # attempt to remove any memory limits
4287 $FreeMemory = $this->GetFreeMemory();
4288 ini_set("memory_limit", -1);
4289
4290 # if there is a background task currently running
4291 if (isset($this->RunningTask))
4292 {
4293 # add info about current page load
4294 $CrashInfo["ElapsedTime"] = $this->GetElapsedExecutionTime();
4295 $CrashInfo["FreeMemory"] = $FreeMemory;
4296 $CrashInfo["REMOTE_ADDR"] = $_SERVER["REMOTE_ADDR"];
4297 $CrashInfo["REQUEST_URI"] = $_SERVER["REQUEST_URI"];
4298 if (isset($_SERVER["REQUEST_TIME"]))
4299 {
4300 $CrashInfo["REQUEST_TIME"] = $_SERVER["REQUEST_TIME"];
4301 }
4302 if (isset($_SERVER["REMOTE_HOST"]))
4303 {
4304 $CrashInfo["REMOTE_HOST"] = $_SERVER["REMOTE_HOST"];
4305 }
4306
4307 # add info about error that caused crash (if available)
4308 if (function_exists("error_get_last"))
4309 {
4310 $CrashInfo["LastError"] = error_get_last();
4311 }
4312
4313 # add info about current output buffer contents (if available)
4314 if (ob_get_length() !== FALSE)
4315 {
4316 $CrashInfo["OutputBuffer"] = ob_get_contents();
4317 }
4318
4319 # if backtrace info is available for the crash
4320 $Backtrace = debug_backtrace();
4321 if (count($Backtrace) > 1)
4322 {
4323 # discard the current context from the backtrace
4324 array_shift($Backtrace);
4325
4326 # add the backtrace to the crash info
4327 $CrashInfo["Backtrace"] = $Backtrace;
4328 }
4329 # else if saved backtrace info is available
4330 elseif (isset($this->SavedContext))
4331 {
4332 # add the saved backtrace to the crash info
4333 $CrashInfo["Backtrace"] = $this->SavedContext;
4334 }
4335
4336 # save crash info for currently running task
4337 $DB = new Database();
4338 $DB->Query("UPDATE RunningTasks SET CrashInfo = '"
4339 .addslashes(serialize($CrashInfo))
4340 ."' WHERE TaskId = ".intval($this->RunningTask["TaskId"]));
4341 }
4342
4343 print("\n");
4344 return;
4345 }
4346
4363 private function AddToDirList($DirList, $Dir, $SearchLast, $SkipSlashCheck)
4364 {
4365 # convert incoming directory to array of directories (if needed)
4366 $Dirs = is_array($Dir) ? $Dir : array($Dir);
4367
4368 # reverse array so directories are searched in specified order
4369 $Dirs = array_reverse($Dirs);
4370
4371 # for each directory
4372 foreach ($Dirs as $Location)
4373 {
4374 # make sure directory includes trailing slash
4375 if (!$SkipSlashCheck)
4376 {
4377 $Location = $Location
4378 .((substr($Location, -1) != "/") ? "/" : "");
4379 }
4380
4381 # remove directory from list if already present
4382 if (in_array($Location, $DirList))
4383 {
4384 $DirList = array_diff(
4385 $DirList, array($Location));
4386 }
4387
4388 # add directory to list of directories
4389 if ($SearchLast)
4390 {
4391 array_push($DirList, $Location);
4392 }
4393 else
4394 {
4395 array_unshift($DirList, $Location);
4396 }
4397 }
4398
4399 # return updated directory list to caller
4400 return $DirList;
4401 }
4402
4410 private function ArrayPermutations($Items, $Perms = array())
4411 {
4412 if (empty($Items))
4413 {
4414 $Result = array($Perms);
4415 }
4416 else
4417 {
4418 $Result = array();
4419 for ($Index = count($Items) - 1; $Index >= 0; --$Index)
4420 {
4421 $NewItems = $Items;
4422 $NewPerms = $Perms;
4423 list($Segment) = array_splice($NewItems, $Index, 1);
4424 array_unshift($NewPerms, $Segment);
4425 $Result = array_merge($Result,
4426 $this->ArrayPermutations($NewItems, $NewPerms));
4427 }
4428 }
4429 return $Result;
4430 }
4431
4438 private function OutputModificationCallbackShell($Matches)
4439 {
4440 # call previously-stored external function
4441 return call_user_func($this->OutputModificationCallbackInfo["Callback"],
4442 $Matches,
4443 $this->OutputModificationCallbackInfo["Pattern"],
4444 $this->OutputModificationCallbackInfo["Page"],
4445 $this->OutputModificationCallbackInfo["SearchPattern"]);
4446 }
4447
4454 function UpdateSetting($FieldName, $NewValue = DB_NOVALUE)
4455 {
4456 return $this->DB->UpdateValue("ApplicationFrameworkSettings",
4457 $FieldName, $NewValue, NULL, $this->Settings);
4458 }
4459
4461 private $InterfaceDirList = array(
4462 "local/interface/%ACTIVEUI%/",
4463 "interface/%ACTIVEUI%/",
4464 "local/interface/default/",
4465 "interface/default/",
4466 );
4471 private $IncludeDirList = array(
4472 "local/interface/%ACTIVEUI%/include/",
4473 "interface/%ACTIVEUI%/include/",
4474 "interface/%ACTIVEUI%/objects/",
4475 "local/interface/default/include/",
4476 "interface/default/include/",
4477 "interface/default/objects/",
4478 );
4480 private $ImageDirList = array(
4481 "local/interface/%ACTIVEUI%/images/",
4482 "interface/%ACTIVEUI%/images/",
4483 "local/interface/default/images/",
4484 "interface/default/images/",
4485 );
4487 private $FunctionDirList = array(
4488 "local/interface/%ACTIVEUI%/include/",
4489 "interface/%ACTIVEUI%/include/",
4490 "local/interface/default/include/",
4491 "interface/default/include/",
4492 "local/include/",
4493 "include/",
4494 );
4495
4496 const NOVALUE = ".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
4497
4498
4499 # ---- Page Caching (Internal Methods) -----------------------------------
4500
4506 private function CheckForCachedPage($PageName)
4507 {
4508 # assume no cached page will be found
4509 $CachedPage = NULL;
4510
4511 # if returning a cached page is allowed
4512 if ($this->CacheCurrentPage)
4513 {
4514 # get fingerprint for requested page
4515 $PageFingerprint = $this->GetPageFingerprint($PageName);
4516
4517 # look for matching page in cache in database
4518 $this->DB->Query("SELECT * FROM AF_CachedPages"
4519 ." WHERE Fingerprint = '".addslashes($PageFingerprint)."'");
4520
4521 # if matching page found
4522 if ($this->DB->NumRowsSelected())
4523 {
4524 # if cached page has expired
4525 $Row = $this->DB->FetchRow();
4526 $ExpirationTime = strtotime(
4527 "-".$this->PageCacheExpirationPeriod()." seconds");
4528 if (strtotime($Row["CachedAt"]) < $ExpirationTime)
4529 {
4530 # clear expired pages from cache
4531 $ExpirationTimestamp = date("Y-m-d H:i:s", $ExpirationTime);
4532 $this->DB->Query("DELETE CP, CPTI FROM AF_CachedPages CP,"
4533 ." AF_CachedPageTagInts CPTI"
4534 ." WHERE CP.CachedAt < '".$ExpirationTimestamp."'"
4535 ." AND CPTI.CacheId = CP.CacheId");
4536 $this->DB->Query("DELETE FROM AF_CachedPages "
4537 ." WHERE CachedAt < '".$ExpirationTimestamp."'");
4538 }
4539 else
4540 {
4541 # display cached page and exit
4542 $CachedPage = $Row["PageContent"];
4543 }
4544 }
4545 }
4546
4547 # return any cached page found to caller
4548 return $CachedPage;
4549 }
4550
4556 private function UpdatePageCache($PageName, $PageContent)
4557 {
4558 # if page caching is enabled and current page should be cached
4559 if ($this->PageCacheEnabled()
4560 && $this->CacheCurrentPage
4561 && ($PageName != "404"))
4562 {
4563 # save page to cache
4564 $PageFingerprint = $this->GetPageFingerprint($PageName);
4565 $this->DB->Query("INSERT INTO AF_CachedPages"
4566 ." (Fingerprint, PageContent) VALUES"
4567 ." ('".$this->DB->EscapeString($PageFingerprint)."', '"
4568 .$this->DB->EscapeString($PageContent)."')");
4569 $CacheId = $this->DB->LastInsertId();
4570
4571 # for each page cache tag that was added
4572 foreach ($this->PageCacheTags as $Tag => $Pages)
4573 {
4574 # if current page is in list for tag
4575 if (in_array("CURRENT", $Pages) || in_array($PageName, $Pages))
4576 {
4577 # look up tag ID
4578 $TagId = $this->GetPageCacheTagId($Tag);
4579
4580 # mark current page as associated with tag
4581 $this->DB->Query("INSERT INTO AF_CachedPageTagInts"
4582 ." (TagId, CacheId) VALUES "
4583 ." (".intval($TagId).", ".intval($CacheId).")");
4584 }
4585 }
4586 }
4587 }
4588
4594 private function GetPageCacheTagId($Tag)
4595 {
4596 # if tag is a non-negative integer
4597 if (is_numeric($Tag) && ($Tag > 0) && (intval($Tag) == $Tag))
4598 {
4599 # generate ID
4600 $Id = self::PAGECACHETAGIDOFFSET + $Tag;
4601 }
4602 else
4603 {
4604 # look up ID in database
4605 $Id = $this->DB->Query("SELECT TagId FROM AF_CachedPageTags"
4606 ." WHERE Tag = '".addslashes($Tag)."'", "TagId");
4607
4608 # if ID was not found
4609 if ($Id === NULL)
4610 {
4611 # add tag to database
4612 $this->DB->Query("INSERT INTO AF_CachedPageTags"
4613 ." SET Tag = '".addslashes($Tag)."'");
4614 $Id = $this->DB->LastInsertId();
4615 }
4616 }
4617
4618 # return tag ID to caller
4619 return $Id;
4620 }
4621
4627 private function GetPageFingerprint($PageName)
4628 {
4629 # only get the environmental fingerprint once so that it is consistent
4630 # between page construction start and end
4631 static $EnvFingerprint;
4632 if (!isset($EnvFingerprint))
4633 {
4634 $EnvFingerprint = md5(json_encode($_GET).json_encode($_POST));
4635 }
4636
4637 # build page fingerprint and return it to caller
4638 return $PageName."-".$EnvFingerprint;
4639 }
4640 }
Message
Abstraction for forum messages and resource comments.
Definition: Message.php:15
Database
SQL database abstraction object with smart query caching.
Definition: Axis--Database.php:22
DB_NOVALUE
const DB_NOVALUE
Definition: Axis--Database.php:1331
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
scssc
SCSS compiler written in PHP.
Definition: scssc.php:45
PHP
PHP
Definition: RSSClient--Test.php:12
JavaScriptPacker
Definition: JavaScriptPacker.php:69
$Items
$Items
Definition: RSSClient--Test.php:26