3 # FILE: ApplicationFramework.php
5 # Part of the ScoutLib application support library
6 # Copyright 2009-2014 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu
16 # ---- PUBLIC INTERFACE --------------------------------------------------
24 function __construct()
26 # save execution start time
27 $this->ExecutionStartTime = microtime(TRUE);
29 # begin/restore PHP session
30 $SessionDomain = isset($_SERVER[
"SERVER_NAME"]) ? $_SERVER[
"SERVER_NAME"]
31 : isset($_SERVER[
"HTTP_HOST"]) ? $_SERVER[
"HTTP_HOST"]
33 if (is_writable(session_save_path()))
35 $SessionStorage = session_save_path()
36 .
"/".self::$AppName.
"_".md5($SessionDomain.dirname(__FILE__));
37 if (!is_dir($SessionStorage)) { mkdir($SessionStorage, 0700 ); }
38 if (is_writable($SessionStorage)) { session_save_path($SessionStorage); }
40 ini_set(
"session.gc_maxlifetime", self::$SessionLifetime);
41 session_set_cookie_params(
42 self::$SessionLifetime,
"/", $SessionDomain);
45 # set up object file autoloader
46 $this->SetUpObjectAutoloading();
48 # set up function to output any buffered text in case of crash
49 register_shutdown_function(array($this,
"OnCrash"));
51 # set up our internal environment
54 # set up our exception handler
55 set_exception_handler(array($this,
"GlobalExceptionHandler"));
57 # perform any work needed to undo PHP magic quotes
58 $this->UndoMagicQuotes();
60 # load our settings from database
61 $this->LoadSettings();
63 # set PHP maximum execution time
66 # register events we handle internally
70 # attempt to create SCSS cache directory if needed and it does not exist
71 if ($this->UseScss && !is_dir(self::$ScssCacheDir))
72 { mkdir(self::$ScssCacheDir, 0777, TRUE); }
82 # if template location cache is flagged to be saved
83 if ($this->SaveTemplateLocationCache)
85 # write template location cache out and update cache expiration
86 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
87 .
" SET TemplateLocationCache = '"
88 .addslashes(serialize(
89 $this->Settings[
"TemplateLocationCache"])).
"',"
90 .
" TemplateLocationCacheExpiration = "
92 .$this->Settings[
"TemplateLocationCacheInterval"]
96 # if object location cache is flagged to be saved
97 if (self::$SaveObjectLocationCache)
99 # write object location cache out and update cache expiration
100 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
101 .
" SET ObjectLocationCache = '"
102 .addslashes(serialize(
103 self::$ObjectLocationCache)).
"',"
104 .
" ObjectLocationCacheExpiration = "
105 .
" NOW() + INTERVAL "
106 .self::$ObjectLocationCacheInterval
116 function GlobalExceptionHandler($Exception)
118 # display exception info
119 $Location = $Exception->getFile().
"[".$Exception->getLine().
"]";
120 ?><table width=
"100%" cellpadding=
"5"
121 style=
"border: 2px solid #666666; background: #CCCCCC;
122 font-family: Courier New, Courier, monospace;
123 margin-top: 10px;"><tr><td>
124 <div style=
"color: #666666;">
125 <span style=
"font-size: 150%;">
126 <b>Uncaught Exception</b></span><br />
127 <b>
Message:</b> <i><?
PHP print $Exception->getMessage(); ?></i><br />
128 <b>Location:</b> <i><?
PHP print $Location; ?></i><br />
130 <blockquote><pre><?
PHP print $Exception->getTraceAsString();
131 ?></pre></blockquote>
133 </td></tr></table><?
PHP
135 # log exception if possible
136 $LogMsg =
"Uncaught exception (".$Exception->getMessage().
").";
137 $this->
LogError(self::LOGLVL_ERROR, $LogMsg);
153 $Dir, $Prefix =
"", $ClassPattern = NULL, $ClassReplacement = NULL)
155 # make sure directory has trailing slash
156 $Dir = $Dir.((substr($Dir, -1) !=
"/") ?
"/" :
"");
158 # add directory to directory list
159 self::$ObjectDirectories = array_merge(
162 "ClassPattern" => $ClassPattern,
163 "ClassReplacement" => $ClassReplacement,
165 self::$ObjectDirectories);
189 # add directories to existing image directory list
190 $this->ImageDirList = $this->AddToDirList(
191 $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
216 # add directories to existing image directory list
217 $this->IncludeDirList = $this->AddToDirList(
218 $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
242 # add directories to existing image directory list
243 $this->InterfaceDirList = $this->AddToDirList(
244 $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
268 # add directories to existing image directory list
269 $this->FunctionDirList = $this->AddToDirList(
270 $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
280 $this->BrowserDetectFunc = $DetectionFunc;
291 if (is_callable($Callback))
293 $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
305 return $this->
UpdateSetting(
"TemplateLocationCacheInterval", $NewInterval);
316 return $this->
UpdateSetting(
"ObjectLocationCacheInterval", $NewInterval);
332 $BacktraceOptions = 0, $BacktraceLimit = 0)
334 if (version_compare(PHP_VERSION,
"5.4.0",
">="))
336 $this->SavedContext = debug_backtrace(
337 $BacktraceOptions, $BacktraceLimit);
341 $this->SavedContext = debug_backtrace($BacktraceOptions);
343 array_shift($this->SavedContext);
352 # perform any clean URL rewriting
353 $PageName = $this->RewriteCleanUrls($PageName);
355 # sanitize incoming page name and save local copy
356 $PageName = preg_replace(
"/[^a-zA-Z0-9_.-]/",
"", $PageName);
357 $this->PageName = $PageName;
359 # buffer any output from includes or PHP file
362 # include any files needed to set up execution environment
363 foreach ($this->EnvIncludes as $IncludeFile)
365 include($IncludeFile);
369 $this->
SignalEvent(
"EVENT_PAGE_LOAD", array(
"PageName" => $PageName));
371 # signal PHP file load
372 $SignalResult = $this->
SignalEvent(
"EVENT_PHP_FILE_LOAD", array(
373 "PageName" => $PageName));
375 # if signal handler returned new page name value
376 $NewPageName = $PageName;
377 if (($SignalResult[
"PageName"] != $PageName)
378 && strlen($SignalResult[
"PageName"]))
380 # if new page name value is page file
381 if (file_exists($SignalResult[
"PageName"]))
383 # use new value for PHP file name
384 $PageFile = $SignalResult[
"PageName"];
388 # use new value for page name
389 $NewPageName = $SignalResult[
"PageName"];
392 # update local copy of page name
393 $this->PageName = $NewPageName;
396 # if we do not already have a PHP file
397 if (!isset($PageFile))
399 # look for PHP file for page
400 $OurPageFile =
"pages/".$NewPageName.
".php";
401 $LocalPageFile =
"local/pages/".$NewPageName.
".php";
402 $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
403 : (file_exists($OurPageFile) ? $OurPageFile
404 :
"pages/".$this->DefaultPage.
".php");
410 # save buffered output to be displayed later after HTML file loads
411 $PageOutput = ob_get_contents();
414 # signal PHP file load is complete
416 $Context[
"Variables"] = get_defined_vars();
418 array(
"PageName" => $PageName,
"Context" => $Context));
419 $PageCompleteOutput = ob_get_contents();
422 # set up for possible TSR (Terminate and Stay Resident :))
423 $ShouldTSR = $this->PrepForTSR();
425 # if PHP file indicated we should autorefresh to somewhere else
426 if ($this->JumpToPage)
428 if (!strlen(trim($PageOutput)))
432 <meta http-equiv=
"refresh" content=
"0; URL=<?PHP
433 print($this->JumpToPage); ?>">
435 <body bgcolor=
"white">
440 # else if HTML loading is not suppressed
441 elseif (!$this->SuppressHTML)
443 # set content-type to get rid of diacritic errors
444 header(
"Content-Type: text/html; charset="
447 # load common HTML file (defines common functions) if available
448 $CommonHtmlFile = $this->FindFile($this->IncludeDirList,
449 "Common", array(
"tpl",
"html"));
450 if ($CommonHtmlFile) { include($CommonHtmlFile); }
453 $this->LoadUIFunctions();
455 # begin buffering content
458 # signal HTML file load
459 $SignalResult = $this->
SignalEvent(
"EVENT_HTML_FILE_LOAD", array(
460 "PageName" => $PageName));
462 # if signal handler returned new page name value
463 $NewPageName = $PageName;
464 $PageContentFile = NULL;
465 if (($SignalResult[
"PageName"] != $PageName)
466 && strlen($SignalResult[
"PageName"]))
468 # if new page name value is HTML file
469 if (file_exists($SignalResult[
"PageName"]))
471 # use new value for HTML file name
472 $PageContentFile = $SignalResult[
"PageName"];
476 # use new value for page name
477 $NewPageName = $SignalResult[
"PageName"];
481 # load page content HTML file if available
482 if ($PageContentFile === NULL)
484 $PageContentFile = $this->FindFile(
485 $this->InterfaceDirList, $NewPageName,
486 array(
"tpl",
"html"));
488 if ($PageContentFile)
490 include($PageContentFile);
494 print
"<h2>ERROR: No HTML/TPL template found"
495 .
" for this page.</h2>";
498 # signal HTML file load complete
499 $SignalResult = $this->
SignalEvent(
"EVENT_HTML_FILE_LOAD_COMPLETE");
501 # stop buffering and save output
502 $PageContentOutput = ob_get_contents();
505 # load page start HTML file if available
507 $PageStartFile = $this->FindFile($this->IncludeDirList,
"Start",
508 array(
"tpl",
"html"), array(
"StdPage",
"StandardPage"));
509 if ($PageStartFile) { include($PageStartFile); }
510 $PageStartOutput = ob_get_contents();
513 # load page end HTML file if available
515 $PageEndFile = $this->FindFile($this->IncludeDirList,
"End",
516 array(
"tpl",
"html"), array(
"StdPage",
"StandardPage"));
517 if ($PageEndFile) { include($PageEndFile); }
518 $PageEndOutput = ob_get_contents();
521 # get list of any required files not loaded
522 $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
524 # if a browser detection function has been made available
525 if (is_callable($this->BrowserDetectFunc))
527 # call function to get browser list
528 $Browsers = call_user_func($this->BrowserDetectFunc);
530 # for each required file
531 $NewRequiredFiles = array();
532 foreach ($RequiredFiles as $File)
534 # if file name includes browser keyword
535 if (preg_match(
"/%BROWSER%/", $File))
538 foreach ($Browsers as $Browser)
540 # substitute in browser name and add to new file list
541 $NewRequiredFiles[] = preg_replace(
542 "/%BROWSER%/", $Browser, $File);
547 # add to new file list
548 $NewRequiredFiles[] = $File;
551 $RequiredFiles = $NewRequiredFiles;
555 # filter out any files with browser keyword in their name
556 $NewRequiredFiles = array();
557 foreach ($RequiredFiles as $File)
559 if (!preg_match(
"/%BROWSER%/", $File))
561 $NewRequiredFiles[] = $File;
564 $RequiredFiles = $NewRequiredFiles;
567 # for each required file
568 foreach ($RequiredFiles as $File)
570 # locate specific file to use
571 $FilePath = $this->
GUIFile($File);
576 # determine file type
577 $NamePieces = explode(
".", $File);
578 $FileSuffix = strtolower(array_pop($NamePieces));
580 # add file to HTML output based on file type
581 $FilePath = htmlspecialchars($FilePath);
585 $Tag =
'<script type="text/javascript" src="'
586 .$FilePath.
'"></script>';
587 $PageEndOutput = preg_replace(
588 "#</body>#i", $Tag.
"\n</body>", $PageEndOutput, 1);
592 $Tag =
'<link rel="stylesheet" type="text/css"'
593 .
' media="all" href="'.$FilePath.
'">';
594 $PageStartOutput = preg_replace(
595 "#</head>#i", $Tag.
"\n</head>", $PageStartOutput, 1);
602 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
604 # perform any regular expression replacements in output
605 $FullPageOutput = preg_replace($this->OutputModificationPatterns,
606 $this->OutputModificationReplacements, $FullPageOutput);
608 # perform any callback replacements in output
609 foreach ($this->OutputModificationCallbacks as $Info)
611 $this->OutputModificationCallbackInfo = $Info;
612 $FullPageOutput = preg_replace_callback($Info[
"SearchPattern"],
613 array($this,
"OutputModificationCallbackShell"),
617 # if relative paths may not work because we were invoked via clean URL
618 if ($this->CleanUrlRewritePerformed || self::WasUrlRewritten())
620 # if using the <base> tag is okay
624 # add <base> tag to header
625 $PageStartOutput = preg_replace(
"%<head>%",
626 "<head><base href=\"".$BaseUrl.
"\" />",
629 # re-assemble full page with new header
630 $FullPageOutput = $PageStartOutput.$PageContentOutput.$PageEndOutput;
632 # the absolute URL to the current page
635 # make HREF attribute values with just a fragment ID
636 # absolute since they don't work with the <base> tag because
637 # they are relative to the current page/URL, not the site
639 $FullPageOutput = preg_replace(
640 array(
"%href=\"(#[^:\" ]+)\"%i",
"%href='(#[^:' ]+)'%i"),
641 array(
"href=\"".$FullUrl.
"$1\"",
"href='".$FullUrl.
"$1'"),
646 # try to fix any relative paths throughout code
647 $FullPageOutput = preg_replace(array(
648 "%src=\"/?([^?*:;{}\\\\\" ]+)\.(js|css|gif|png|jpg)\"%i",
649 "%src='/?([^?*:;{}\\\\' ]+)\.(js|css|gif|png|jpg)'%i",
650 # don
't rewrite HREF attributes that are just
651 # fragment IDs because they are relative to the
652 # current page/URL, not the site root
653 "%href=\"/?([^#][^:\" ]*)\"%i",
654 "%href='/?([^#][^:
' ]*)'%i
",
655 "%action=\
"/?([^#][^:\" ]*)\"%i",
656 "%action='/?([^#][^:' ]*)'%i",
657 "%@import\s+url\(\"/?([^:\" ]+)\"\s*\)%i",
658 "%@import\s+url\('/?([^:\" ]+)'\s*\)%i",
659 "%@import\s+\"/?([^:\" ]+)\"\s*%i",
660 "%@import\s+'/?([^:\" ]+)'\s*%i",
663 "src=\"".$BaseUrl.
"$1.$2\"",
664 "src=\"".$BaseUrl.
"$1.$2\"",
665 "href=\"".$BaseUrl.
"$1\"",
666 "href=\"".$BaseUrl.
"$1\"",
667 "action=\"".$BaseUrl.
"$1\"",
668 "action=\"".$BaseUrl.
"$1\"",
669 "@import url(\"".$BaseUrl.
"$1\")",
670 "@import url('".$BaseUrl.
"$1')",
671 "@import \"".$BaseUrl.
"$1\"",
672 "@import '".$BaseUrl.
"$1'",
678 # provide the opportunity to modify full page output
679 $SignalResult = $this->
SignalEvent(
"EVENT_PAGE_OUTPUT_FILTER", array(
680 "PageOutput" => $FullPageOutput));
681 if (isset($SignalResult[
"PageOutput"])
682 && strlen($SignalResult[
"PageOutput"]))
684 $FullPageOutput = $SignalResult[
"PageOutput"];
687 # write out full page
688 print $FullPageOutput;
691 # run any post-processing routines
692 foreach ($this->PostProcessingFuncs as $Func)
694 call_user_func_array($Func[
"FunctionName"], $Func[
"Arguments"]);
697 # write out any output buffered from page code execution
698 if (strlen($PageOutput))
700 if (!$this->SuppressHTML)
702 ?><table width=
"100%" cellpadding=
"5"
703 style=
"border: 2px solid #666666; background: #CCCCCC;
704 font-family: Courier New, Courier, monospace;
705 margin-top: 10px;"><tr><td><?
PHP
707 if ($this->JumpToPage)
709 ?><div style=
"color: #666666;"><span style=
"font-size: 150%;">
710 <b>Page Jump Aborted</b></span>
711 (because of error or other unexpected output)<br />
713 <i><?
PHP print($this->JumpToPage); ?></i></div><?
PHP
716 if (!$this->SuppressHTML)
718 ?></td></tr></table><?
PHP
722 # write out any output buffered from the page code execution complete signal
723 if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
725 print $PageCompleteOutput;
728 # execute callbacks that should not have their output buffered
729 foreach ($this->UnbufferedCallbacks as $Callback)
731 call_user_func_array($Callback[0], $Callback[1]);
734 # log high memory usage
735 if (function_exists(
"memory_get_peak_usage"))
740 && (memory_get_peak_usage() >= $MemoryThreshold))
742 $HighMemUsageMsg =
"High peak memory usage ("
743 .intval(memory_get_peak_usage()).
") for "
744 .$this->FullUrl().
" from "
745 .$_SERVER[
"REMOTE_ADDR"];
746 $this->
LogMessage(self::LOGLVL_INFO, $HighMemUsageMsg);
750 # log slow page loads
755 $SlowPageLoadMsg =
"Slow page load ("
757 .$this->FullUrl().
" from "
758 .$_SERVER[
"REMOTE_ADDR"];
759 $this->
LogMessage(self::LOGLVL_INFO, $SlowPageLoadMsg);
762 # terminate and stay resident (TSR!) if indicated and HTML has been output
763 # (only TSR if HTML has been output because otherwise browsers will misbehave)
764 if ($ShouldTSR) { $this->LaunchTSR(); }
774 return $this->PageName;
784 # retrieve current URL
787 # remove the base path if present
788 $BasePath = $this->Settings[
"BasePath"];
789 if (stripos($Url, $BasePath) === 0)
791 $Url = substr($Url, strlen($BasePath));
819 && (strpos($Page,
"?") === FALSE)
820 && ((strpos($Page,
"=") !== FALSE)
821 || ((stripos($Page,
".php") === FALSE)
822 && (stripos($Page,
".htm") === FALSE)
823 && (strpos($Page,
"/") === FALSE)))
824 && (stripos($Page,
"http://") !== 0)
825 && (stripos($Page,
"https://") !== 0))
827 $this->JumpToPage = self::BaseUrl() .
"index.php?P=".$Page;
831 $this->JumpToPage = $Page;
841 return ($this->JumpToPage === NULL) ? FALSE : TRUE;
855 if ($NewSetting !== NULL) { $this->
HtmlCharset = $NewSetting; }
856 return $this->HtmlCharset;
868 return $this->UseMinimizedJavascript;
883 if ($NewValue !== NULL) { $this->
UseBaseTag = $NewValue ? TRUE : FALSE; }
884 return $this->UseBaseTag;
895 $this->SuppressHTML = $NewSetting;
906 if ($UIName !== NULL)
908 $this->ActiveUI = preg_replace(
"/^SPTUI--/",
"", $UIName);
910 return $this->ActiveUI;
920 # possible UI directories
921 $InterfaceDirs = array(
925 # start out with an empty list
926 $Interfaces = array();
928 # for each possible UI directory
929 foreach ($InterfaceDirs as $InterfaceDir)
931 $Dir = dir($InterfaceDir);
933 # for each file in current directory
934 while (($DirEntry = $Dir->read()) !== FALSE)
936 $InterfacePath = $InterfaceDir.
"/".$DirEntry;
938 # skip anything that doesn't have a name in the required format
939 if (!preg_match(
'/^[a-zA-Z0-9]+$/', $DirEntry))
944 # skip anything that isn't a directory
945 if (!is_dir($InterfacePath))
950 # read the UI name (if available)
951 $UIName = @file_get_contents($InterfacePath.
"/NAME");
953 # use the directory name if the UI name isn't available
954 if ($UIName === FALSE || !strlen($UIName))
959 $Interfaces[$InterfacePath] = $UIName;
965 # return list to caller
985 &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
986 &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
987 &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
989 $FuncIndex = count($this->PostProcessingFuncs);
990 $this->PostProcessingFuncs[$FuncIndex][
"FunctionName"] = $FunctionName;
991 $this->PostProcessingFuncs[$FuncIndex][
"Arguments"] = array();
993 while (isset(${
"Arg".$Index}) && (${
"Arg".$Index} !== self::NOVALUE))
995 $this->PostProcessingFuncs[$FuncIndex][
"Arguments"][$Index]
1008 $this->EnvIncludes[] = $FileName;
1019 # determine file type (being clever because this is run so often)
1020 static $FTOther = 0;
1022 static $FTImage = 2;
1023 static $FTJavaScript = 3;
1024 $FileSuffix = strtolower(substr($FileName, -3));
1025 if ($FileSuffix ==
"css") { $FileType = $FTCSS; }
1026 elseif ($FileSuffix ==
".js") { $FileType = $FTJavaScript; }
1027 elseif (($FileSuffix ==
"gif")
1028 || ($FileSuffix ==
"jpg")
1029 || ($FileSuffix ==
"png")) { $FileType = $FTImage; }
1030 else { $FileType = $FTOther; }
1032 # determine which location to search based on file suffix
1033 $DirList = ($FileType == $FTImage)
1034 ? $this->ImageDirList : $this->IncludeDirList;
1036 # if directed to get a minimized JavaScript file
1037 if (($FileType == $FTJavaScript) && $this->UseMinimizedJavascript)
1039 # first try to find the minimized JavaScript file
1040 $MinimizedFileName = substr_replace($FileName,
".min", -3, 0);
1041 $FoundFileName = $this->FindFile($DirList, $MinimizedFileName);
1043 # search for the regular file if a minimized file wasn't found
1044 if (is_null($FoundFileName))
1046 $FoundFileName = $this->FindFile($DirList, $FileName);
1049 # else if directed to use SCSS files
1050 elseif (($FileType == $FTCSS) && $this->UseScss)
1052 # look for SCSS version of file
1053 $SourceFileName = preg_replace(
"/.css$/",
".scss", $FileName);
1054 $FoundSourceFileName = $this->FindFile($DirList, $SourceFileName);
1056 # if SCSS file not found
1057 if ($FoundSourceFileName === NULL)
1060 $FoundFileName = $this->FindFile($DirList, $FileName);
1064 # compile SCSS file (if updated) and return resulting CSS file
1065 $FoundFileName = $this->CompileScssFile($FoundSourceFileName);
1068 # otherwise just search for the file
1071 $FoundFileName = $this->FindFile($DirList, $FileName);
1074 # add non-image files to list of found files (used for required files loading)
1075 if ($FileType != $FTImage)
1076 { $this->FoundUIFiles[] = basename($FoundFileName); }
1078 # return file name to caller
1079 return $FoundFileName;
1093 $FullFileName = $this->
GUIFile($FileName);
1094 if ($FullFileName) { print($FullFileName); }
1106 $this->AdditionalRequiredUIFiles[] = $FileName;
1119 # if specified function is not currently available
1120 if (!is_callable($Callback))
1122 # if function info looks legal
1123 if (is_string($Callback) && strlen($Callback))
1125 # start with function directory list
1126 $Locations = $this->FunctionDirList;
1128 # add object directories to list
1129 $Locations = array_merge(
1130 $Locations, array_keys(self::$ObjectDirectories));
1132 # look for function file
1133 $FunctionFileName = $this->FindFile($Locations,
"F-".$Callback,
1134 array(
"php",
"html"));
1136 # if function file was found
1137 if ($FunctionFileName)
1139 # load function file
1140 include_once($FunctionFileName);
1144 # log error indicating function load failed
1145 $this->
LogError(self::LOGLVL_ERROR,
"Unable to load function"
1146 .
" for callback \"".$Callback.
"\".");
1151 # log error indicating specified function info was bad
1152 $this->
LogError(self::LOGLVL_ERROR,
"Unloadable callback value"
1154 .
" passed to AF::LoadFunction().");
1158 # report to caller whether function load succeeded
1159 return is_callable($Callback);
1168 return microtime(TRUE) - $this->ExecutionStartTime;
1183 # ---- Logging -----------------------------------------------------------
1211 return $this->
UpdateSetting(
"SlowPageLoadThreshold", $NewValue);
1226 return $this->
UpdateSetting(
"LogHighMemoryUsage", $NewValue);
1239 return $this->
UpdateSetting(
"HighMemoryUsageThreshold", $NewValue);
1257 # if error level is at or below current logging level
1258 if ($this->Settings[
"LoggingLevel"] >= $Level)
1260 # attempt to log error message
1263 # if logging attempt failed and level indicated significant error
1264 if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
1266 # throw exception about inability to log error
1267 static $AlreadyThrewException = FALSE;
1268 if (!$AlreadyThrewException)
1270 $AlreadyThrewException = TRUE;
1271 throw new Exception(
"Unable to log error (".$Level.
": ".$Msg.
").");
1275 # report to caller whether message was logged
1280 # report to caller that message was not logged
1298 # if message level is at or below current logging level
1299 if ($this->Settings[
"LoggingLevel"] >= $Level)
1301 # attempt to open log file
1302 $FHndl = @fopen($this->LogFileName,
"a");
1304 # if log file could not be open
1305 if ($FHndl === FALSE)
1307 # report to caller that message was not logged
1313 $ErrorAbbrevs = array(
1314 self::LOGLVL_FATAL =>
"FTL",
1315 self::LOGLVL_ERROR =>
"ERR",
1316 self::LOGLVL_WARNING =>
"WRN",
1317 self::LOGLVL_INFO =>
"INF",
1318 self::LOGLVL_DEBUG =>
"DBG",
1319 self::LOGLVL_TRACE =>
"TRC",
1321 $LogEntry = date(
"Y-m-d H:i:s")
1322 .
" ".($this->RunningInBackground ?
"B" :
"F")
1323 .
" ".$ErrorAbbrevs[$Level]
1326 # write entry to log
1327 $Success = fwrite($FHndl, $LogEntry.
"\n");
1332 # report to caller whether message was logged
1333 return ($Success === FALSE) ? FALSE : TRUE;
1338 # report to caller that message was not logged
1366 # constrain new level (if supplied) to within legal bounds
1369 $NewValue = max(min($NewValue, 6), 1);
1372 # set new logging level (if supplied) and return current level to caller
1384 if ($NewValue !== NULL) { $this->LogFileName = $NewValue; }
1385 return $this->LogFileName;
1424 # ---- Event Handling ----------------------------------------------------
1470 # convert parameters to array if not already in that form
1471 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1472 : array($EventsOrEventName => $EventType);
1475 foreach ($Events as $Name => $Type)
1477 # store event information
1478 $this->RegisteredEvents[$Name][
"Type"] = $Type;
1479 $this->RegisteredEvents[$Name][
"Hooks"] = array();
1491 return array_key_exists($EventName, $this->RegisteredEvents)
1503 # the event isn't hooked to if it isn't even registered
1509 # return TRUE if there is at least one callback hooked to the event
1510 return count($this->RegisteredEvents[$EventName][
"Hooks"]) > 0;
1526 function HookEvent($EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
1528 # convert parameters to array if not already in that form
1529 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1530 : array($EventsOrEventName => $Callback);
1534 foreach ($Events as $EventName => $EventCallback)
1536 # if callback is valid
1537 if (is_callable($EventCallback))
1539 # if this is a periodic event we process internally
1540 if (isset($this->PeriodicEvents[$EventName]))
1543 $this->ProcessPeriodicEvent($EventName, $EventCallback);
1545 # if specified event has been registered
1546 elseif (isset($this->RegisteredEvents[$EventName]))
1548 # add callback for event
1549 $this->RegisteredEvents[$EventName][
"Hooks"][]
1550 = array(
"Callback" => $EventCallback,
"Order" => $Order);
1552 # sort callbacks by order
1553 if (count($this->RegisteredEvents[$EventName][
"Hooks"]) > 1)
1555 usort($this->RegisteredEvents[$EventName][
"Hooks"],
1556 array(
"ApplicationFramework",
"HookEvent_OrderCompare"));
1570 # report to caller whether all callbacks were hooked
1574 private static function HookEvent_OrderCompare($A, $B)
1576 if ($A[
"Order"] == $B[
"Order"]) {
return 0; }
1577 return ($A[
"Order"] < $B[
"Order"]) ? -1 : 1;
1592 $ReturnValue = NULL;
1594 # if event has been registered
1595 if (isset($this->RegisteredEvents[$EventName]))
1597 # set up default return value (if not NULL)
1598 switch ($this->RegisteredEvents[$EventName][
"Type"])
1600 case self::EVENTTYPE_CHAIN:
1601 $ReturnValue = $Parameters;
1604 case self::EVENTTYPE_NAMED:
1605 $ReturnValue = array();
1609 # for each callback for this event
1610 foreach ($this->RegisteredEvents[$EventName][
"Hooks"] as $Hook)
1613 $Callback = $Hook[
"Callback"];
1614 $Result = ($Parameters !== NULL)
1615 ? call_user_func_array($Callback, $Parameters)
1616 : call_user_func($Callback);
1618 # process return value based on event type
1619 switch ($this->RegisteredEvents[$EventName][
"Type"])
1621 case self::EVENTTYPE_CHAIN:
1622 if ($Result !== NULL)
1624 foreach ($Parameters as $Index => $Value)
1626 if (array_key_exists($Index, $Result))
1628 $Parameters[$Index] = $Result[$Index];
1631 $ReturnValue = $Parameters;
1635 case self::EVENTTYPE_FIRST:
1636 if ($Result !== NULL)
1638 $ReturnValue = $Result;
1643 case self::EVENTTYPE_NAMED:
1644 $CallbackName = is_array($Callback)
1645 ? (is_object($Callback[0])
1646 ? get_class($Callback[0])
1647 : $Callback[0]).
"::".$Callback[1]
1649 $ReturnValue[$CallbackName] = $Result;
1659 $this->
LogError(self::LOGLVL_WARNING,
1660 "Unregistered event signaled (".$EventName.
").");
1663 # return value if any to caller
1664 return $ReturnValue;
1674 return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
1689 # if event is not a periodic event report failure to caller
1690 if (!array_key_exists($EventName, $this->EventPeriods)) {
return FALSE; }
1692 # retrieve last execution time for event if available
1693 $Signature = self::GetCallbackSignature($Callback);
1694 $LastRunTime = $this->DB->Query(
"SELECT LastRunAt FROM PeriodicEvents"
1695 .
" WHERE Signature = '".addslashes($Signature).
"'",
"LastRunAt");
1697 # if event was not found report failure to caller
1698 if ($LastRunTime === NULL) {
return FALSE; }
1700 # calculate next run time based on event period
1701 $NextRunTime = strtotime($LastRunTime) + $this->EventPeriods[$EventName];
1703 # report next run time to caller
1704 return $NextRunTime;
1724 # retrieve last execution times
1725 $this->DB->Query(
"SELECT * FROM PeriodicEvents");
1726 $LastRunTimes = $this->DB->FetchColumn(
"LastRunAt",
"Signature");
1728 # for each known event
1730 foreach ($this->KnownPeriodicEvents as $Signature => $Info)
1732 # if last run time for event is available
1733 if (array_key_exists($Signature, $LastRunTimes))
1735 # calculate next run time for event
1736 $LastRun = strtotime($LastRunTimes[$Signature]);
1737 $NextRun = $LastRun + $this->EventPeriods[$Info[
"Period"]];
1738 if ($Info[
"Period"] ==
"EVENT_PERIODIC") { $LastRun = FALSE; }
1742 # set info to indicate run times are not known
1747 # add event info to list
1748 $Events[$Signature] = $Info;
1749 $Events[$Signature][
"LastRun"] = $LastRun;
1750 $Events[$Signature][
"NextRun"] = $NextRun;
1751 $Events[$Signature][
"Parameters"] = NULL;
1754 # return list of known events to caller
1761 # ---- Task Management ---------------------------------------------------
1787 $Priority = self::PRIORITY_LOW, $Description =
"")
1789 # pack task info and write to database
1790 if ($Parameters === NULL) { $Parameters = array(); }
1791 $this->DB->Query(
"INSERT INTO TaskQueue"
1792 .
" (Callback, Parameters, Priority, Description)"
1793 .
" VALUES ('".addslashes(serialize($Callback)).
"', '"
1794 .addslashes(serialize($Parameters)).
"', ".intval($Priority).
", '"
1795 .addslashes($Description).
"')");
1816 $Priority = self::PRIORITY_LOW, $Description =
"")
1820 $QueryResult = $this->DB->Query(
"SELECT TaskId,Priority FROM TaskQueue"
1821 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'"
1822 .($Parameters ?
" AND Parameters = '"
1823 .addslashes(serialize($Parameters)).
"'" :
""));
1824 if ($QueryResult !== FALSE)
1826 $Record = $this->DB->FetchRow();
1827 if ($Record[
"Priority"] > $Priority)
1829 $this->DB->Query(
"UPDATE TaskQueue"
1830 .
" SET Priority = ".intval($Priority)
1831 .
" WHERE TaskId = ".intval($Record[
"TaskId"]));
1838 $this->
QueueTask($Callback, $Parameters, $Priority, $Description);
1854 $QueuedCount = $this->DB->Query(
1855 "SELECT COUNT(*) AS FoundCount FROM TaskQueue"
1856 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'"
1857 .($Parameters ?
" AND Parameters = '"
1858 .addslashes(serialize($Parameters)).
"'" :
""),
1860 $RunningCount = $this->DB->Query(
1861 "SELECT COUNT(*) AS FoundCount FROM RunningTasks"
1862 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'"
1863 .($Parameters ?
" AND Parameters = '"
1864 .addslashes(serialize($Parameters)).
"'" :
""),
1866 $FoundCount = $QueuedCount + $RunningCount;
1867 return ($FoundCount ? TRUE : FALSE);
1889 return $this->GetTaskList(
"SELECT * FROM TaskQueue"
1890 .
" ORDER BY Priority, TaskId ", $Count, $Offset);
1907 $Parameters = NULL, $Priority = NULL, $Description = NULL)
1909 $Query =
"SELECT COUNT(*) AS TaskCount FROM TaskQueue";
1911 if ($Callback !== NULL)
1913 $Query .= $Sep.
" Callback = '".addslashes(serialize($Callback)).
"'";
1916 if ($Parameters !== NULL)
1918 $Query .= $Sep.
" Parameters = '".addslashes(serialize($Parameters)).
"'";
1921 if ($Priority !== NULL)
1923 $Query .= $Sep.
" Priority = ".intval($Priority);
1926 if ($Description !== NULL)
1928 $Query .= $Sep.
" Description = '".addslashes($Description).
"'";
1930 return $this->DB->Query($Query,
"TaskCount");
1942 return $this->GetTaskList(
"SELECT * FROM RunningTasks"
1943 .
" WHERE StartedAt >= '".date(
"Y-m-d H:i:s",
1944 (time() - ini_get(
"max_execution_time"))).
"'"
1945 .
" ORDER BY StartedAt", $Count, $Offset);
1957 return $this->GetTaskList(
"SELECT * FROM RunningTasks"
1958 .
" WHERE StartedAt < '".date(
"Y-m-d H:i:s",
1959 (time() - ini_get(
"max_execution_time"))).
"'"
1960 .
" ORDER BY StartedAt", $Count, $Offset);
1969 return $this->DB->Query(
"SELECT COUNT(*) AS Count FROM RunningTasks"
1970 .
" WHERE StartedAt < '".date(
"Y-m-d H:i:s",
1971 (time() - ini_get(
"max_execution_time"))).
"'",
1982 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
1983 $this->DB->Query(
"INSERT INTO TaskQueue"
1984 .
" (Callback,Parameters,Priority,Description) "
1985 .
"SELECT Callback, Parameters, Priority, Description"
1986 .
" FROM RunningTasks WHERE TaskId = ".intval($TaskId));
1987 if ($NewPriority !== NULL)
1989 $NewTaskId = $this->DB->LastInsertId();
1990 $this->DB->Query(
"UPDATE TaskQueue SET Priority = "
1991 .intval($NewPriority)
1992 .
" WHERE TaskId = ".intval($NewTaskId));
1994 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
1995 $this->DB->Query(
"UNLOCK TABLES");
2004 $this->DB->Query(
"DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2005 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
2017 # assume task will not be found
2020 # look for task in task queue
2021 $this->DB->Query(
"SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
2023 # if task was not found in queue
2024 if (!$this->DB->NumRowsSelected())
2026 # look for task in running task list
2027 $this->DB->Query(
"SELECT * FROM RunningTasks WHERE TaskId = "
2032 if ($this->DB->NumRowsSelected())
2034 # if task was periodic
2035 $Row = $this->DB->FetchRow();
2036 if ($Row[
"Callback"] ==
2037 serialize(array(
"ApplicationFramework",
"PeriodicEventWrapper")))
2039 # unpack periodic task callback
2040 $WrappedCallback = unserialize($Row[
"Parameters"]);
2041 $Task[
"Callback"] = $WrappedCallback[1];
2042 $Task[
"Parameters"] = $WrappedCallback[2];
2046 # unpack task callback and parameters
2047 $Task[
"Callback"] = unserialize($Row[
"Callback"]);
2048 $Task[
"Parameters"] = unserialize($Row[
"Parameters"]);
2052 # return task to caller
2065 return $this->
UpdateSetting(
"TaskExecutionEnabled", $NewValue);
2087 if (func_num_args() && !ini_get(
"safe_mode"))
2089 if ($NewValue != $this->Settings[
"MaxExecTime"])
2091 $this->Settings[
"MaxExecTime"] = max($NewValue, 5);
2092 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
2093 .
" SET MaxExecTime = '"
2094 .intval($this->Settings[
"MaxExecTime"]).
"'");
2096 ini_set(
"max_execution_time", $this->Settings[
"MaxExecTime"]);
2097 set_time_limit($this->Settings[
"MaxExecTime"]);
2099 return ini_get(
"max_execution_time");
2105 # ---- Clean URL Support -------------------------------------------------
2135 function AddCleanUrl($Pattern, $Page, $GetVars = NULL, $Template = NULL)
2137 # save clean URL mapping parameters
2138 $this->CleanUrlMappings[] = array(
2139 "Pattern" => $Pattern,
2141 "GetVars" => $GetVars,
2144 # if replacement template specified
2145 if ($Template !== NULL)
2147 # if GET parameters specified
2148 if (count($GetVars))
2150 # retrieve all possible permutations of GET parameters
2151 $GetPerms = $this->ArrayPermutations(array_keys($GetVars));
2153 # for each permutation of GET parameters
2154 foreach ($GetPerms as $VarPermutation)
2156 # construct search pattern for permutation
2157 $SearchPattern =
"/href=([\"'])index\\.php\\?P=".$Page;
2158 $GetVarSegment =
"";
2159 foreach ($VarPermutation as $GetVar)
2161 if (preg_match(
"%\\\$[0-9]+%", $GetVars[$GetVar]))
2163 $GetVarSegment .=
"&".$GetVar.
"=((?:(?!\\1)[^&])+)";
2167 $GetVarSegment .=
"&".$GetVar.
"=".$GetVars[$GetVar];
2170 $SearchPattern .= $GetVarSegment.
"\\1/i";
2172 # if template is actually a callback
2173 if (is_callable($Template))
2175 # add pattern to HTML output mod callbacks list
2176 $this->OutputModificationCallbacks[] = array(
2177 "Pattern" => $Pattern,
2179 "SearchPattern" => $SearchPattern,
2180 "Callback" => $Template,
2185 # construct replacement string for permutation
2186 $Replacement = $Template;
2188 foreach ($VarPermutation as $GetVar)
2190 $Replacement = str_replace(
2191 "\$".$GetVar,
"\$".$Index, $Replacement);
2194 $Replacement =
"href=\"".$Replacement.
"\"";
2196 # add pattern to HTML output modifications list
2197 $this->OutputModificationPatterns[] = $SearchPattern;
2198 $this->OutputModificationReplacements[] = $Replacement;
2204 # construct search pattern
2205 $SearchPattern =
"/href=\"index\\.php\\?P=".$Page.
"\"/i";
2207 # if template is actually a callback
2208 if (is_callable($Template))
2210 # add pattern to HTML output mod callbacks list
2211 $this->OutputModificationCallbacks[] = array(
2212 "Pattern" => $Pattern,
2214 "SearchPattern" => $SearchPattern,
2215 "Callback" => $Template,
2220 # add simple pattern to HTML output modifications list
2221 $this->OutputModificationPatterns[] = $SearchPattern;
2222 $this->OutputModificationReplacements[] =
"href=\"".$Template.
"\"";
2235 foreach ($this->CleanUrlMappings as $Info)
2237 if (preg_match($Info[
"Pattern"], $Path))
2253 # the search patterns and callbacks require a specific format
2254 $Format =
"href=\"".str_replace(
"&",
"&", $Path).
"\"";
2257 # perform any regular expression replacements on the search string
2258 $Search = preg_replace(
2259 $this->OutputModificationPatterns,
2260 $this->OutputModificationReplacements,
2263 # only run the callbacks if a replacement hasn't already been performed
2264 if ($Search == $Format)
2266 # perform any callback replacements on the search string
2267 foreach ($this->OutputModificationCallbacks as $Info)
2269 # make the information available to the callback
2270 $this->OutputModificationCallbackInfo = $Info;
2272 # execute the callback
2273 $Search = preg_replace_callback(
2274 $Info[
"SearchPattern"],
2275 array($this,
"OutputModificationCallbackShell"),
2280 # return the path untouched if no replacements were performed
2281 if ($Search == $Format)
2286 # remove the bits added to the search string to get it recognized by
2287 # the replacement expressions and callbacks
2288 $Result = substr($Search, 6, -1);
2301 # for each clean URL mapping
2302 foreach ($this->CleanUrlMappings as $Info)
2304 # if current path matches the clean URL pattern
2305 if (preg_match($Info[
"Pattern"], $Path, $Matches))
2307 # the GET parameters for the URL, starting with the page name
2308 $GetVars = array(
"P" => $Info[
"Page"]);
2310 # if additional $_GET variables specified for clean URL
2311 if ($Info[
"GetVars"] !== NULL)
2313 # for each $_GET variable specified for clean URL
2314 foreach ($Info[
"GetVars"] as $VarName => $VarTemplate)
2316 # start with template for variable value
2317 $Value = $VarTemplate;
2319 # for each subpattern matched in current URL
2320 foreach ($Matches as $Index => $Match)
2322 # if not first (whole) match
2325 # make any substitutions in template
2326 $Value = str_replace(
"$".$Index, $Match, $Value);
2330 # add the GET variable
2331 $GetVars[$VarName] = $Value;
2335 # return the unclean URL
2336 return "index.php?" . http_build_query($GetVars);
2340 # return the path unchanged
2360 $GetVars = array(
"P" => $this->
GetPageName()) + $_GET;
2361 return "index.php?" . http_build_query($GetVars);
2367 # ---- Server Environment ------------------------------------------------
2378 if ($NewValue !== NULL)
2380 self::$SessionLifetime = $NewValue;
2382 return self::$SessionLifetime;
2392 # HTACCESS_SUPPORT is set in the .htaccess file
2393 return isset($_SERVER[
"HTACCESS_SUPPORT"]);
2405 # return override value if one is set
2406 if (self::$RootUrlOverride !== NULL)
2408 return self::$RootUrlOverride;
2411 # determine scheme name
2412 $Protocol = (isset($_SERVER[
"HTTPS"]) ?
"https" :
"http");
2414 # if HTTP_HOST is preferred or SERVER_NAME points to localhost
2415 # and HTTP_HOST is set
2416 if ((self::$PreferHttpHost || ($_SERVER[
"SERVER_NAME"] ==
"127.0.0.1"))
2417 && isset($_SERVER[
"HTTP_HOST"]))
2419 # use HTTP_HOST for domain name
2420 $DomainName = $_SERVER[
"HTTP_HOST"];
2424 # use SERVER_NAME for domain name
2425 $DomainName = $_SERVER[
"HTTP_HOST"];
2428 # build URL root and return to caller
2429 return $Protocol.
"://".$DomainName;
2448 if ($NewValue !== self::NOVALUE)
2450 self::$RootUrlOverride = strlen(trim($NewValue)) ? $NewValue : NULL;
2452 return self::$RootUrlOverride;
2466 $BaseUrl = self::RootUrl().dirname($_SERVER[
"SCRIPT_NAME"]);
2467 if (substr($BaseUrl, -1) !=
"/") { $BaseUrl .=
"/"; }
2480 return self::RootUrl().$_SERVER[
"REQUEST_URI"];
2495 if ($NewValue !== NULL)
2497 self::$PreferHttpHost = ($NewValue ? TRUE : FALSE);
2499 return self::$PreferHttpHost;
2508 $BasePath = dirname($_SERVER[
"SCRIPT_NAME"]);
2510 if (substr($BasePath, -1) !=
"/")
2525 if (array_key_exists(
"SCRIPT_URL", $_SERVER))
2527 return $_SERVER[
"SCRIPT_URL"];
2529 elseif (array_key_exists(
"REDIRECT_URL", $_SERVER))
2531 return $_SERVER[
"REDIRECT_URL"];
2533 elseif (array_key_exists(
"REQUEST_URI", $_SERVER))
2535 $Pieces = parse_url($_SERVER[
"REQUEST_URI"]);
2536 return $Pieces[
"path"];
2554 # needed to get the path of the URL minus the query and fragment pieces
2555 $Components = parse_url(self::GetScriptUrl());
2557 # if parsing was successful and a path is set
2558 if (is_array($Components) && isset($Components[
"path"]))
2560 $BasePath = self::BasePath();
2561 $Path = $Components[
"path"];
2563 # the URL was rewritten if the path isn't the base path, i.e., the
2564 # home page, and the file in the URL isn't the script generating the
2566 if ($BasePath != $Path && basename($Path) != $ScriptName)
2572 # the URL wasn't rewritten
2583 return self::GetPhpMemoryLimit() - memory_get_usage();
2593 $Str = strtoupper(ini_get(
"memory_limit"));
2594 if (substr($Str, -1) ==
"B") { $Str = substr($Str, 0, strlen($Str) - 1); }
2595 switch (substr($Str, -1))
2597 case "K": $MemoryLimit = (int)$Str * 1024;
break;
2598 case "M": $MemoryLimit = (int)$Str * 1048576;
break;
2599 case "G": $MemoryLimit = (int)$Str * 1073741824;
break;
2600 default: $MemoryLimit = (int)$Str;
break;
2602 return $MemoryLimit;
2608 # ---- Backward Compatibility --------------------------------------------
2618 return $this->FindFile(
2619 $this->IncludeDirList, $BaseName, array(
"tpl",
"html"));
2625 # ---- PRIVATE INTERFACE -------------------------------------------------
2627 private $ActiveUI =
"default";
2628 private $BrowserDetectFunc;
2629 private $CleanUrlMappings = array();
2630 private $CleanUrlRewritePerformed = FALSE;
2632 private $DefaultPage =
"Home";
2633 private $EnvIncludes = array();
2634 private $ExecutionStartTime;
2635 private $FoundUIFiles = array();
2636 private $AdditionalRequiredUIFiles = array();
2637 private $GenerateCompactCss = TRUE;
2638 private $HtmlCharset =
"UTF-8";
2639 private $JumpToPage = NULL;
2640 private $LogFileName =
"local/logs/site.log";
2641 private $MaxRunningTasksToTrack = 250;
2642 private $OutputModificationPatterns = array();
2643 private $OutputModificationReplacements = array();
2644 private $OutputModificationCallbacks = array();
2645 private $OutputModificationCallbackInfo;
2647 private $PostProcessingFuncs = array();
2648 private $RunningInBackground = FALSE;
2649 private $RunningTask;
2650 private $SavedContext;
2652 private $SuppressHTML = FALSE;
2653 private $SaveTemplateLocationCache = FALSE;
2654 private $UnbufferedCallbacks = array();
2655 private $UseBaseTag = FALSE;
2656 private $UseMinimizedJavascript = FALSE;
2657 private $UseScss = TRUE;
2659 private static $AppName =
"ScoutAF";
2660 private static $ObjectDirectories = array();
2661 private static $ObjectLocationCache;
2662 private static $ObjectLocationCacheInterval = 60;
2663 private static $ObjectLocationCacheExpiration;
2664 private static $PreferHttpHost = FALSE;
2665 private static $RootUrlOverride = NULL;
2666 private static $SaveObjectLocationCache = FALSE;
2667 private static $ScssCacheDir =
"local/data/caches/SCSS";
2668 private static $SessionLifetime = 1440;
2674 private $NoTSR = FALSE;
2676 private $KnownPeriodicEvents = array();
2677 private $PeriodicEvents = array(
2678 "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
2679 "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
2680 "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
2681 "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
2682 "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
2684 private $EventPeriods = array(
2685 "EVENT_HOURLY" => 3600,
2686 "EVENT_DAILY" => 86400,
2687 "EVENT_WEEKLY" => 604800,
2688 "EVENT_MONTHLY" => 2592000,
2689 "EVENT_PERIODIC" => 0,
2691 private $UIEvents = array(
2692 "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
2693 "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
2694 "EVENT_PHP_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
2695 "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
2696 "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
2697 "EVENT_PAGE_OUTPUT_FILTER" => self::EVENTTYPE_CHAIN,
2703 private function LoadSettings()
2705 # read settings in from database
2706 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
2707 $this->Settings = $this->DB->FetchRow();
2709 # if settings were not previously initialized
2710 if (!$this->Settings)
2712 # initialize settings in database
2713 $this->DB->Query(
"INSERT INTO ApplicationFrameworkSettings"
2714 .
" (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
2716 # read new settings in from database
2717 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
2718 $this->Settings = $this->DB->FetchRow();
2721 # if base path was not previously set or we appear to have moved
2722 if (!array_key_exists(
"BasePath", $this->Settings)
2723 || (!strlen($this->Settings[
"BasePath"]))
2724 || (!array_key_exists(
"BasePathCheck", $this->Settings))
2725 || (__FILE__ != $this->Settings[
"BasePathCheck"]))
2727 # attempt to extract base path from Apache .htaccess file
2728 if (is_readable(
".htaccess"))
2730 $Lines = file(
".htaccess");
2731 foreach ($Lines as $Line)
2733 if (preg_match(
"/\\s*RewriteBase\\s+/", $Line))
2735 $Pieces = preg_split(
2736 "/\\s+/", $Line, NULL, PREG_SPLIT_NO_EMPTY);
2737 $BasePath = $Pieces[1];
2742 # if base path was found
2743 if (isset($BasePath))
2745 # save base path locally
2746 $this->Settings[
"BasePath"] = $BasePath;
2748 # save base path to database
2749 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
2750 .
" SET BasePath = '".addslashes($BasePath).
"'"
2751 .
", BasePathCheck = '".addslashes(__FILE__).
"'");
2755 # if template location cache has been saved to database
2756 if (isset($this->Settings[
"TemplateLocationCache"]))
2758 # unserialize cache values into array and use if valid
2759 $Cache = unserialize($this->Settings[
"TemplateLocationCache"]);
2760 $this->Settings[
"TemplateLocationCache"] =
2761 count($Cache) ? $Cache : array();
2765 # start with empty cache
2766 $this->Settings[
"TemplateLocationCache"] = array();
2769 # if object location cache has been saved to database
2770 if (isset($this->Settings[
"ObjectLocationCache"]))
2772 # unserialize cache values into array and use if valid
2773 $Cache = unserialize($this->Settings[
"ObjectLocationCache"]);
2774 $this->Settings[
"ObjectLocationCache"] =
2775 count($Cache) ? $Cache : array();
2777 # store static versions for use when autoloading objects
2778 self::$ObjectLocationCache =
2779 $this->Settings[
"ObjectLocationCache"];
2780 self::$ObjectLocationCacheInterval =
2781 $this->Settings[
"ObjectLocationCacheInterval"];
2782 self::$ObjectLocationCacheExpiration =
2783 $this->Settings[
"ObjectLocationCacheExpiration"];
2787 # start with empty cache
2788 $this->Settings[
"ObjectLocationCache"] = array();
2798 private function RewriteCleanUrls($PageName)
2800 # if URL rewriting is supported by the server
2803 # retrieve current URL and remove base path if present
2806 # for each clean URL mapping
2807 foreach ($this->CleanUrlMappings as $Info)
2809 # if current URL matches clean URL pattern
2810 if (preg_match($Info[
"Pattern"], $Url, $Matches))
2813 $PageName = $Info[
"Page"];
2815 # if $_GET variables specified for clean URL
2816 if ($Info[
"GetVars"] !== NULL)
2818 # for each $_GET variable specified for clean URL
2819 foreach ($Info[
"GetVars"] as $VarName => $VarTemplate)
2821 # start with template for variable value
2822 $Value = $VarTemplate;
2824 # for each subpattern matched in current URL
2825 foreach ($Matches as $Index => $Match)
2827 # if not first (whole) match
2830 # make any substitutions in template
2831 $Value = str_replace(
"$".$Index, $Match, $Value);
2835 # set $_GET variable
2836 $_GET[$VarName] = $Value;
2840 # set flag indicating clean URL mapped
2841 $this->CleanUrlRewritePerformed = TRUE;
2843 # stop looking for a mapping
2849 # return (possibly) updated page name to caller
2871 private function FindFile($DirectoryList, $BaseName,
2872 $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
2874 # generate template cache index for this page
2875 $CacheIndex = md5(serialize($DirectoryList))
2876 .
":".$this->ActiveUI.
":".$BaseName;
2878 # if we have cached location and cache expiration time has not elapsed
2879 if (($this->Settings[
"TemplateLocationCacheInterval"] > 0)
2880 && count($this->Settings[
"TemplateLocationCache"])
2881 && array_key_exists($CacheIndex,
2882 $this->Settings[
"TemplateLocationCache"])
2883 && (time() < strtotime(
2884 $this->Settings[
"TemplateLocationCacheExpiration"])))
2886 # use template location from cache
2887 $FoundFileName = $this->Settings[
2888 "TemplateLocationCache"][$CacheIndex];
2892 # if suffixes specified and base name does not include suffix
2893 if (count($PossibleSuffixes)
2894 && !preg_match(
"/\.[a-zA-Z0-9]+$/", $BaseName))
2896 # add versions of file names with suffixes to file name list
2897 $FileNames = array();
2898 foreach ($PossibleSuffixes as $Suffix)
2900 $FileNames[] = $BaseName.
".".$Suffix;
2905 # use base name as file name
2906 $FileNames = array($BaseName);
2909 # if prefixes specified
2910 if (count($PossiblePrefixes))
2912 # add versions of file names with prefixes to file name list
2913 $NewFileNames = array();
2914 foreach ($FileNames as $FileName)
2916 foreach ($PossiblePrefixes as $Prefix)
2918 $NewFileNames[] = $Prefix.$FileName;
2921 $FileNames = $NewFileNames;
2924 # for each possible location
2925 $FoundFileName = NULL;
2926 foreach ($DirectoryList as $Dir)
2928 # substitute active UI name into path
2929 $Dir = str_replace(
"%ACTIVEUI%", $this->ActiveUI, $Dir);
2931 # for each possible file name
2932 foreach ($FileNames as $File)
2934 # if template is found at location
2935 if (file_exists($Dir.$File))
2937 # save full template file name and stop looking
2938 $FoundFileName = $Dir.$File;
2944 # save location in cache
2945 $this->Settings[
"TemplateLocationCache"][$CacheIndex]
2948 # set flag indicating that cache should be saved
2949 $this->SaveTemplateLocationCache = TRUE;
2952 # return full template file name to caller
2953 return $FoundFileName;
2963 private function CompileScssFile($SrcFile)
2965 # build path to CSS file
2966 $DstFile =
"local/data/caches/SCSS/"
2967 .str_replace(
"/",
"_", dirname($SrcFile))
2968 .
"_".basename($SrcFile);
2969 $DstFile = substr_replace($DstFile,
"css", -4);
2971 # if SCSS file is newer than CSS file
2972 if (!file_exists($DstFile)
2973 || (filemtime($SrcFile) > filemtime($DstFile)))
2975 # if CSS cache directory and CSS file path appear writable
2976 static $CacheDirIsWritable;
2977 if (!isset($CacheDirIsWritable))
2978 { $CacheDirIsWritable = is_writable(self::$ScssCacheDir); }
2979 if (is_writable($DstFile)
2980 || (!file_exists($DstFile) && $CacheDirIsWritable))
2982 # compile SCSS file to CSS file
2983 $ScssCompiler =
new scssc();
2984 $ScssCompiler->setFormatter($this->GenerateCompactCss
2985 ?
"scss_formatter_compressed" :
"scss_formatter");
2986 $ScssCode = file_get_contents($SrcFile);
2989 $CssCode = $ScssCompiler->compile($ScssCode);
2990 file_put_contents($DstFile, $CssCode);
2992 catch (Exception $Ex)
2994 $this->
LogError(self::LOGLVL_ERROR,
2995 "Error compiling SCSS file ".$SrcFile.
": "
2996 .$Ex->getMessage());
3002 # log error and set CSS file path to indicate failure
3003 $this->
LogError(self::LOGLVL_ERROR,
3004 "Unable to write out CSS file for SCSS file ".$SrcFile);
3009 # return CSS file path to caller
3019 private function GetRequiredFilesNotYetLoaded($PageContentFile)
3021 # start out assuming no files required
3022 $RequiredFiles = array();
3024 # if page content file supplied
3025 if ($PageContentFile)
3027 # if file containing list of required files is available
3028 $Path = dirname($PageContentFile);
3029 $RequireListFile = $Path.
"/REQUIRES";
3030 if (file_exists($RequireListFile))
3032 # read in list of required files
3033 $RequestedFiles = file($RequireListFile);
3035 # for each line in required file list
3036 foreach ($RequestedFiles as $Line)
3038 # if line is not a comment
3039 $Line = trim($Line);
3040 if (!preg_match(
"/^#/", $Line))
3042 # if file has not already been loaded
3043 if (!in_array($Line, $this->FoundUIFiles))
3045 # add to list of required files
3046 $RequiredFiles[] = $Line;
3053 # add in additional required files if any
3054 if (count($this->AdditionalRequiredUIFiles))
3056 # make sure there are no duplicates
3057 $AdditionalRequiredUIFiles = array_unique(
3058 $this->AdditionalRequiredUIFiles);
3060 $RequiredFiles = array_merge(
3061 $RequiredFiles, $AdditionalRequiredUIFiles);
3064 # return list of required files to caller
3065 return $RequiredFiles;
3071 private function SetUpObjectAutoloading()
3074 function __autoload($ClassName)
3087 # if caching is not turned off
3088 # and we have a cached location for class
3089 # and cache expiration has not elapsed
3090 # and file at cached location is readable
3091 if ((self::$ObjectLocationCacheInterval > 0)
3092 && count(self::$ObjectLocationCache)
3093 && array_key_exists($ClassName,
3094 self::$ObjectLocationCache)
3095 && (time() < strtotime(
3096 self::$ObjectLocationCacheExpiration))
3097 && is_readable(self::$ObjectLocationCache[$ClassName]))
3099 # use object location from cache
3100 require_once(self::$ObjectLocationCache[$ClassName]);
3104 # for each possible object file directory
3106 foreach (self::$ObjectDirectories as $Location => $Info)
3108 # if directory looks valid
3109 if (is_dir($Location))
3111 # build class file name
3112 $NewClassName = ($Info[
"ClassPattern"] && $Info[
"ClassReplacement"])
3113 ? preg_replace($Info[
"ClassPattern"],
3114 $Info[
"ClassReplacement"], $ClassName)
3117 # read in directory contents if not already retrieved
3118 if (!isset($FileLists[$Location]))
3120 $FileLists[$Location] = self::ReadDirectoryTree(
3121 $Location,
'/^.+\.php$/i');
3124 # for each file in target directory
3125 $FileNames = $FileLists[$Location];
3126 $TargetName = strtolower($Info[
"Prefix"].$NewClassName.
".php");
3127 foreach ($FileNames as $FileName)
3129 # if file matches our target object file name
3130 if (strtolower($FileName) == $TargetName)
3132 # include object file
3133 require_once($Location.$FileName);
3135 # save location to cache
3136 self::$ObjectLocationCache[$ClassName]
3137 = $Location.$FileName;
3139 # set flag indicating that cache should be saved
3140 self::$SaveObjectLocationCache = TRUE;
3159 private static function ReadDirectoryTree($Directory, $Pattern)
3161 $CurrentDir = getcwd();
3163 $DirIter =
new RecursiveDirectoryIterator(
".");
3164 $IterIter =
new RecursiveIteratorIterator($DirIter);
3165 $RegexResults =
new RegexIterator($IterIter, $Pattern,
3166 RecursiveRegexIterator::GET_MATCH);
3167 $FileList = array();
3168 foreach ($RegexResults as $Result)
3170 $FileList[] = substr($Result[0], 2);
3179 private function UndoMagicQuotes()
3181 # if this PHP version has magic quotes support
3182 if (version_compare(PHP_VERSION,
"5.4.0",
"<"))
3184 # turn off runtime magic quotes if on
3185 if (get_magic_quotes_runtime())
3188 set_magic_quotes_runtime(FALSE);
3192 # if magic quotes GPC is on
3193 if (get_magic_quotes_gpc())
3195 # strip added slashes from incoming variables
3196 $GPC = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
3197 array_walk_recursive($GPC,
3198 array($this,
"UndoMagicQuotes_StripCallback"));
3202 private function UndoMagicQuotes_StripCallback(&$Value)
3204 $Value = stripslashes($Value);
3211 private function LoadUIFunctions()
3214 "local/interface/%ACTIVEUI%/include",
3215 "interface/%ACTIVEUI%/include",
3216 "local/interface/default/include",
3217 "interface/default/include",
3219 foreach ($Dirs as $Dir)
3221 $Dir = str_replace(
"%ACTIVEUI%", $this->ActiveUI, $Dir);
3224 $FileNames = scandir($Dir);
3225 foreach ($FileNames as $FileName)
3227 if (preg_match(
"/^F-([A-Za-z0-9_]+)\.php/", $FileName, $Matches)
3228 || preg_match(
"/^F-([A-Za-z0-9_]+)\.html/", $FileName, $Matches))
3230 if (!function_exists($Matches[1]))
3232 include_once($Dir.
"/".$FileName);
3245 private function ProcessPeriodicEvent($EventName, $Callback)
3247 # retrieve last execution time for event if available
3248 $Signature = self::GetCallbackSignature($Callback);
3249 $LastRun = $this->DB->Query(
"SELECT LastRunAt FROM PeriodicEvents"
3250 .
" WHERE Signature = '".addslashes($Signature).
"'",
"LastRunAt");
3252 # determine whether enough time has passed for event to execute
3253 $ShouldExecute = (($LastRun === NULL)
3254 || (time() > (strtotime($LastRun) + $this->EventPeriods[$EventName])))
3257 # if event should run
3260 # add event to task queue
3261 $WrapperCallback = array(
"ApplicationFramework",
"PeriodicEventWrapper");
3262 $WrapperParameters = array(
3263 $EventName, $Callback, array(
"LastRunAt" => $LastRun));
3267 # add event to list of periodic events
3268 $this->KnownPeriodicEvents[$Signature] = array(
3269 "Period" => $EventName,
3270 "Callback" => $Callback,
3271 "Queued" => $ShouldExecute);
3281 private static function PeriodicEventWrapper($EventName, $Callback, $Parameters)
3284 if (!isset($DB)) { $DB =
new Database(); }
3287 $ReturnVal = call_user_func_array($Callback, $Parameters);
3289 # if event is already in database
3290 $Signature = self::GetCallbackSignature($Callback);
3291 if ($DB->Query(
"SELECT COUNT(*) AS EventCount FROM PeriodicEvents"
3292 .
" WHERE Signature = '".addslashes($Signature).
"'",
"EventCount"))
3294 # update last run time for event
3295 $DB->Query(
"UPDATE PeriodicEvents SET LastRunAt = "
3296 .(($EventName ==
"EVENT_PERIODIC")
3297 ?
"'".date(
"Y-m-d H:i:s", time() + ($ReturnVal * 60)).
"'"
3299 .
" WHERE Signature = '".addslashes($Signature).
"'");
3303 # add last run time for event to database
3304 $DB->Query(
"INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES "
3305 .
"('".addslashes($Signature).
"', "
3306 .(($EventName ==
"EVENT_PERIODIC")
3307 ?
"'".date(
"Y-m-d H:i:s", time() + ($ReturnVal * 60)).
"'"
3317 private static function GetCallbackSignature($Callback)
3319 return !is_array($Callback) ? $Callback
3320 : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
3328 private function PrepForTSR()
3330 # if HTML has been output and it's time to launch another task
3331 # (only TSR if HTML has been output because otherwise browsers
3332 # may misbehave after connection is closed)
3333 if (($this->JumpToPage || !$this->SuppressHTML)
3334 && (time() > (strtotime($this->Settings[
"LastTaskRunAt"])
3335 + (ini_get(
"max_execution_time")
3336 / $this->Settings[
"MaxTasksRunning"]) + 5))
3338 && $this->Settings[
"TaskExecutionEnabled"])
3340 # begin buffering output for TSR
3343 # let caller know it is time to launch another task
3348 # let caller know it is not time to launch another task
3357 private function LaunchTSR()
3359 # set headers to close out connection to browser
3362 ignore_user_abort(TRUE);
3363 header(
"Connection: close");
3364 header(
"Content-Length: ".ob_get_length());
3367 # output buffered content
3368 while (ob_get_level()) { ob_end_flush(); }
3371 # write out any outstanding data and end HTTP session
3372 session_write_close();
3374 # set flag indicating that we are now running in background
3375 $this->RunningInBackground = TRUE;
3377 # if there is still a task in the queue
3380 # turn on output buffering to (hopefully) record any crash output
3383 # lock tables and grab last task run time to double check
3384 $this->DB->Query(
"LOCK TABLES ApplicationFrameworkSettings WRITE");
3385 $this->LoadSettings();
3387 # if still time to launch another task
3388 if (time() > (strtotime($this->Settings[
"LastTaskRunAt"])
3389 + (ini_get(
"max_execution_time")
3390 / $this->Settings[
"MaxTasksRunning"]) + 5))
3392 # update the "last run" time and release tables
3393 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings"
3394 .
" SET LastTaskRunAt = '".date(
"Y-m-d H:i:s").
"'");
3395 $this->DB->Query(
"UNLOCK TABLES");
3397 # run tasks while there is a task in the queue and enough time left
3401 $this->RunNextTask();
3409 $this->DB->Query(
"UNLOCK TABLES");
3421 private function GetTaskList($DBQuery, $Count, $Offset)
3423 $this->DB->Query($DBQuery.
" LIMIT ".intval($Offset).
",".intval($Count));
3425 while ($Row = $this->DB->FetchRow())
3427 $Tasks[$Row[
"TaskId"]] = $Row;
3428 if ($Row[
"Callback"] ==
3429 serialize(array(
"ApplicationFramework",
"PeriodicEventWrapper")))
3431 $WrappedCallback = unserialize($Row[
"Parameters"]);
3432 $Tasks[$Row[
"TaskId"]][
"Callback"] = $WrappedCallback[1];
3433 $Tasks[$Row[
"TaskId"]][
"Parameters"] = NULL;
3437 $Tasks[$Row[
"TaskId"]][
"Callback"] = unserialize($Row[
"Callback"]);
3438 $Tasks[$Row[
"TaskId"]][
"Parameters"] = unserialize($Row[
"Parameters"]);
3447 private function RunNextTask()
3449 # lock tables to prevent same task from being run by multiple sessions
3450 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
3452 # look for task at head of queue
3453 $this->DB->Query(
"SELECT * FROM TaskQueue ORDER BY Priority, TaskId LIMIT 1");
3454 $Task = $this->DB->FetchRow();
3456 # if there was a task available
3459 # move task from queue to running tasks list
3460 $this->DB->Query(
"INSERT INTO RunningTasks "
3461 .
"(TaskId,Callback,Parameters,Priority,Description) "
3462 .
"SELECT * FROM TaskQueue WHERE TaskId = "
3463 .intval($Task[
"TaskId"]));
3464 $this->DB->Query(
"DELETE FROM TaskQueue WHERE TaskId = "
3465 .intval($Task[
"TaskId"]));
3467 # release table locks to again allow other sessions to run tasks
3468 $this->DB->Query(
"UNLOCK TABLES");
3470 # unpack stored task info
3471 $Callback = unserialize($Task[
"Callback"]);
3472 $Parameters = unserialize($Task[
"Parameters"]);
3474 # attempt to load task callback if not already available
3478 $this->RunningTask = $Task;
3481 call_user_func_array($Callback, $Parameters);
3485 call_user_func($Callback);
3487 unset($this->RunningTask);
3489 # remove task from running tasks list
3490 $this->DB->Query(
"DELETE FROM RunningTasks"
3491 .
" WHERE TaskId = ".intval($Task[
"TaskId"]));
3493 # prune running tasks list if necessary
3494 $RunningTasksCount = $this->DB->Query(
3495 "SELECT COUNT(*) AS TaskCount FROM RunningTasks",
"TaskCount");
3496 if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
3498 $this->DB->Query(
"DELETE FROM RunningTasks ORDER BY StartedAt"
3499 .
" LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
3504 # release table locks to again allow other sessions to run tasks
3505 $this->DB->Query(
"UNLOCK TABLES");
3516 # attempt to remove any memory limits
3517 ini_set(
"memory_limit", -1);
3519 # if there is a background task currently running
3520 if (isset($this->RunningTask))
3522 # add info about error that caused crash (if available)
3523 if (function_exists(
"error_get_last"))
3525 $CrashInfo[
"LastError"] = error_get_last();
3528 # add info about current output buffer contents (if available)
3529 if (ob_get_length() !== FALSE)
3531 $CrashInfo[
"OutputBuffer"] = ob_get_contents();
3534 # if backtrace info is available for the crash
3535 $Backtrace = debug_backtrace();
3536 if (count($Backtrace) > 1)
3538 # discard the current context from the backtrace
3539 array_shift($Backtrace);
3541 # add the backtrace to the crash info
3542 $CrashInfo[
"Backtrace"] = $Backtrace;
3544 # else if saved backtrace info is available
3545 elseif (isset($this->SavedContext))
3547 # add the saved backtrace to the crash info
3548 $CrashInfo[
"Backtrace"] = $this->SavedContext;
3551 # if we have crash info to recod
3552 if (isset($CrashInfo))
3554 # save crash info for currently running task
3556 $DB->Query(
"UPDATE RunningTasks SET CrashInfo = '"
3557 .addslashes(serialize($CrashInfo))
3558 .
"' WHERE TaskId = ".intval($this->RunningTask[
"TaskId"]));
3582 private function AddToDirList($DirList, $Dir, $SearchLast, $SkipSlashCheck)
3584 # convert incoming directory to array of directories (if needed)
3585 $Dirs = is_array($Dir) ? $Dir : array($Dir);
3587 # reverse array so directories are searched in specified order
3588 $Dirs = array_reverse($Dirs);
3590 # for each directory
3591 foreach ($Dirs as $Location)
3593 # make sure directory includes trailing slash
3594 if (!$SkipSlashCheck)
3596 $Location = $Location
3597 .((substr($Location, -1) !=
"/") ?
"/" :
"");
3600 # remove directory from list if already present
3601 if (in_array($Location, $DirList))
3603 $DirList = array_diff(
3604 $DirList, array($Location));
3607 # add directory to list of directories
3610 array_push($DirList, $Location);
3614 array_unshift($DirList, $Location);
3618 # return updated directory list to caller
3629 private function ArrayPermutations(
$Items, $Perms = array())
3633 $Result = array($Perms);
3638 for ($Index = count(
$Items) - 1; $Index >= 0; --$Index)
3642 list($Segment) = array_splice($NewItems, $Index, 1);
3643 array_unshift($NewPerms, $Segment);
3644 $Result = array_merge($Result,
3645 $this->ArrayPermutations($NewItems, $NewPerms));
3657 private function OutputModificationCallbackShell($Matches)
3659 # call previously-stored external function
3660 return call_user_func($this->OutputModificationCallbackInfo[
"Callback"],
3662 $this->OutputModificationCallbackInfo[
"Pattern"],
3663 $this->OutputModificationCallbackInfo[
"Page"],
3664 $this->OutputModificationCallbackInfo[
"SearchPattern"]);
3675 return $this->DB->UpdateValue(
"ApplicationFrameworkSettings",
3676 $FieldName, $NewValue, NULL, $this->Settings);
3680 private $InterfaceDirList = array(
3681 "local/interface/%ACTIVEUI%/",
3682 "interface/%ACTIVEUI%/",
3683 "local/interface/default/",
3684 "interface/default/",
3690 private $IncludeDirList = array(
3691 "local/interface/%ACTIVEUI%/include/",
3692 "interface/%ACTIVEUI%/include/",
3693 "local/interface/default/include/",
3694 "interface/default/include/",
3697 private $ImageDirList = array(
3698 "local/interface/%ACTIVEUI%/images/",
3699 "interface/%ACTIVEUI%/images/",
3700 "local/interface/default/images/",
3701 "interface/default/images/",
3704 private $FunctionDirList = array(
3705 "local/interface/%ACTIVEUI%/include/",
3706 "interface/%ACTIVEUI%/include/",
3707 "local/interface/default/include/",
3708 "interface/default/include/",
3713 const NOVALUE =
".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
MaxTasks($NewValue=DB_NOVALUE)
Get/set maximum number of tasks to have running simultaneously.
const LOGLVL_ERROR
ERROR error logging level.
GetOrphanedTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently in queue.
SuppressHTMLOutput($NewSetting=TRUE)
Suppress loading of HTML files.
AddInterfaceDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for user interface (HTML/TPL) files.
AddIncludeDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for user interface include (CSS, JavaScript, common PHP, common HTML, etc) files.
static GetScriptUrl()
Retrieve SCRIPT_URL server value, pulling it from elsewhere if that variable isn't set...
const LOGLVL_INFO
INFO error logging level.
AddUnbufferedCallback($Callback, $Parameters=array())
Add a callback that will be executed after buffered content has been output and that won't have its o...
QueueUniqueTask($Callback, $Parameters=NULL, $Priority=self::PRIORITY_LOW, $Description="")
Add task to queue if not already in queue or currently running.
const LOGLVL_FATAL
FATAL error logging level.
UseMinimizedJavascript($NewSetting=NULL)
Get/set whether or not to check for and use minimized JavaScript files when getting a JavaScript UI f...
AddPostProcessingCall($FunctionName, &$Arg1=self::NOVALUE, &$Arg2=self::NOVALUE, &$Arg3=self::NOVALUE, &$Arg4=self::NOVALUE, &$Arg5=self::NOVALUE, &$Arg6=self::NOVALUE, &$Arg7=self::NOVALUE, &$Arg8=self::NOVALUE, &$Arg9=self::NOVALUE)
Add function to be called after HTML has been loaded.
GetCleanUrlForPath($Path)
Get the clean URL mapped for a path.
const PRIORITY_LOW
Lower priority.
Abstraction for forum messages and resource comments.
GetQueuedTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently in queue.
LogFile($NewValue=NULL)
Get/set log file name.
GetCleanUrl()
Get the clean URL for the current page if one is available.
MaxExecutionTime($NewValue=NULL)
Get/set maximum PHP execution time.
static PreferHttpHost($NewValue=NULL)
Get/set whether to prefer $_SERVER["HTTP_HOST"] (if available) over $_SERVER["SERVER_NAME"] when dete...
RequireUIFile($FileName)
Add file to list of required UI files.
static FullUrl()
Get current full URL, before any clean URL remapping and with any query string (e.g.
Top-level framework for web applications.
static BaseUrl()
Get current base URL (the part before index.php) (e.g.
GetOrphanedTaskCount()
Retrieve current number of orphaned tasks.
SQL database abstraction object with smart query caching.
LogSlowPageLoads($NewValue=DB_NOVALUE)
Get/set whether logging of long page load times is enabled.
SlowPageLoadThreshold($NewValue=DB_NOVALUE)
Get/set how long a page load can take before it should be considered "slow" and may be logged...
GetTaskQueueSize($Priority=NULL)
Retrieve current number of tasks in queue.
static RootUrlOverride($NewValue=self::NOVALUE)
Get/set root URL override.
GetQueuedTaskCount($Callback=NULL, $Parameters=NULL, $Priority=NULL, $Description=NULL)
Get number of queued tasks that match supplied values.
static AutoloadObjects($ClassName)
DeleteTask($TaskId)
Remove task from task queues.
const LOGLVL_DEBUG
DEBUG error logging leve.
const EVENTTYPE_NAMED
Named result event type.
const EVENTTYPE_FIRST
First response event type.
static WasUrlRewritten($ScriptName="index.php")
Determine if the URL was rewritten, i.e., the script is being accessed through a URL that isn't direc...
const EVENTTYPE_DEFAULT
Default event type.
GetPageUrl()
Get the full URL to the page.
IsStaticOnlyEvent($EventName)
Report whether specified event only allows static callbacks.
IsRegisteredEvent($EventName)
Check if event has been registered (is available to be signaled).
static AddObjectDirectory($Dir, $Prefix="", $ClassPattern=NULL, $ClassReplacement=NULL)
Add directory to be searched for object files when autoloading.
SignalEvent($EventName, $Parameters=NULL)
Signal that an event has occured.
static BasePath()
Get current base path (usually the part after the host name).
const LOGLVL_TRACE
TRACE error logging level.
const LOGLVL_WARNING
WARNING error logging level.
const PRIORITY_MEDIUM
Medium (default) priority.
LogError($Level, $Msg)
Write error message to log.
OnCrash()
Called automatically at program termination to ensure output is written out.
GetKnownPeriodicEvents()
Get list of known periodic events.
const EVENTTYPE_CHAIN
Result chaining event type.
SCSS compiler written in PHP.
GetSecondsBeforeTimeout()
Get remaining available (PHP) execution time.
TaskIsInQueue($Callback, $Parameters=NULL)
Check if task is already in queue or currently running.
AddFunctionDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for function ("F-") files.
LoggingLevel($NewValue=DB_NOVALUE)
Get/set logging level.
const ORDER_MIDDLE
Run hooked function after ORDER_FIRST and before ORDER_LAST events.
RegisterEvent($EventsOrEventName, $EventType=NULL)
Register one or more events that may be signaled.
SetJumpToPage($Page, $IsLiteral=FALSE)
Set URL of page to autoload after PHP page file is executed.
GetPageName()
Get name of page being loaded.
CleanUrlIsMapped($Path)
Report whether clean URL has already been mapped.
const PRIORITY_HIGH
Highest priority.
LoadFunction($Callback)
Attempt to load code for function or method if not currently available.
GetRunningTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently in queue.
FindCommonTemplate($BaseName)
Preserved for backward compatibility for use with code written prior to October 2012.
EventWillNextRunAt($EventName, $Callback)
Get date/time a periodic event will next run.
static SessionLifetime($NewValue=NULL)
Get/set session timeout in seconds.
HookEvent($EventsOrEventName, $Callback=NULL, $Order=self::ORDER_MIDDLE)
Hook one or more functions to be called when the specified event is signaled.
IsHookedEvent($EventName)
Check if an event is registered and is hooked to.
static GetFreeMemory()
Get current amount of free memory.
AddImageDirectories($Dir, $SearchLast=FALSE, $SkipSlashCheck=FALSE)
Add additional directory(s) to be searched for image files.
HtmlCharset($NewSetting=NULL)
Get/set HTTP character encoding value.
const ORDER_FIRST
Run hooked function first (i.e.
GetUncleanUrlForPath($Path)
Get the unclean URL for mapped for a path.
TemplateLocationCacheExpirationInterval($NewInterval=DB_NOVALUE)
Get/set UI template location cache expiration period in minutes.
GetUncleanUrl()
Get the unclean URL for the current page.
RecordContextInCaseOfCrash($BacktraceOptions=0, $BacktraceLimit=0)
Record the current execution context in case of crash.
JumpToPageIsSet()
Report whether a page to autoload has been set.
const ORDER_LAST
Run hooked function last (i.e.
UseBaseTag($NewValue=NULL)
Get/set whether or not to use the "base" tag to ensure relative URL paths are correct.
static HtaccessSupport()
Determine if .htaccess files are enabled.
UpdateSetting($FieldName, $NewValue=DB_NOVALUE)
Convenience function for getting/setting our settings.
GetTask($TaskId)
Retrieve task info from queue (either running or queued tasks).
TaskExecutionEnabled($NewValue=DB_NOVALUE)
Get/set whether automatic task execution is enabled.
static GetPhpMemoryLimit()
Get PHP memory limit in bytes.
LoadPage($PageName)
Load page PHP and HTML/TPL files.
AddEnvInclude($FileName)
Add file to be included to set up environment.
ReQueueOrphanedTask($TaskId, $NewPriority=NULL)
Move orphaned task back into queue.
GUIFile($FileName)
Search UI directories for specified image or CSS file and return name of correct file.
QueueTask($Callback, $Parameters=NULL, $Priority=self::PRIORITY_LOW, $Description="")
Add task to queue.
GetUserInterfaces()
Get the list of available user interfaces.
PUIFile($FileName)
Search UI directories for specified image or CSS file and print name of correct file.
LogHighMemoryUsage($NewValue=DB_NOVALUE)
Get/set whether logging of high memory usage is enabled.
ActiveUserInterface($UIName=NULL)
Get/set name of current active user interface.
GetPageLocation()
Get the URL path to the page without the base path, if present.
ObjectLocationCacheExpirationInterval($NewInterval=DB_NOVALUE)
Get/set object file location cache expiration period in minutes.
GetElapsedExecutionTime()
Get time elapsed since constructor was called.
SetBrowserDetectionFunc($DetectionFunc)
Specify function to use to detect the web browser type.
static RootUrl()
Get portion of current URL through host name, with no trailing slash (e.g.
AddCleanUrl($Pattern, $Page, $GetVars=NULL, $Template=NULL)
Add clean URL mapping.
HighMemoryUsageThreshold($NewValue=DB_NOVALUE)
Get/set what percentage of max memory (set via the memory_limit PHP configuration directive) a page l...
const PRIORITY_BACKGROUND
Lowest priority.
LogMessage($Level, $Msg)
Write status message to log.