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 #
4 # FILE: ApplicationFramework.php
5 #
6 # Part of the ScoutLib application support library
7 # Copyright 2009-2012 Edward Almasy and Internet Scout
8 # http://scout.wisc.edu
9 #
10
15 class ApplicationFramework {
16
17 # ---- PUBLIC INTERFACE --------------------------------------------------
18 /*@(*/
20
25 function __construct()
26 {
27 # save execution start time
28 $this->ExecutionStartTime = microtime(TRUE);
29
30 # begin/restore PHP session
31 $SessionPath = isset($_SERVER["REQUEST_URI"])
32 ? dirname($_SERVER["REQUEST_URI"])
33 : isset($_SERVER["SCRIPT_NAME"])
34 ? dirname($_SERVER["SCRIPT_NAME"])
35 : isset($_SERVER["PHP_SELF"])
36 ? dirname($_SERVER["PHP_SELF"])
37 : "";
38 $SessionDomain = isset($_SERVER["SERVER_NAME"]) ? $_SERVER["SERVER_NAME"]
39 : isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"]
40 : php_uname("n");
41 $SessionStorage = session_save_path()
42 ."/".self::$AppName."_".md5($SessionDomain.$SessionPath);
43 if (!is_dir($SessionStorage)) { mkdir($SessionStorage, 0700 ); }
44 session_save_path($SessionStorage);
45 ini_set("session.gc_maxlifetime", self::$SessionLifetime);
46 session_set_cookie_params(
47 self::$SessionLifetime, $SessionPath, $SessionDomain);
48 session_start();
49
50 # set up object file autoloader
51 $this->SetUpObjectAutoloading();
52
53 # set up function to output any buffered text in case of crash
54 register_shutdown_function(array($this, "OnCrash"));
55
56 # set up our internal environment
57 $this->DB = new Database();
58
59 # set up our exception handler
60 set_exception_handler(array($this, "GlobalExceptionHandler"));
61
62 # load our settings from database
63 $this->LoadSettings();
64
65 # set PHP maximum execution time
66 $this->MaxExecutionTime($this->Settings["MaxExecTime"]);
67
68 # register events we handle internally
69 $this->RegisterEvent($this->PeriodicEvents);
70 $this->RegisterEvent($this->UIEvents);
71 }
78 function __destruct()
79 {
80 # if template location cache is flagged to be saved
81 if ($this->SaveTemplateLocationCache)
82 {
83 # write template location cache out and update cache expiration
84 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
85 ." SET TemplateLocationCache = '"
86 .addslashes(serialize(
87 $this->Settings["TemplateLocationCache"]))."',"
88 ." TemplateLocationCacheExpiration = "
89 ." NOW() + INTERVAL "
90 .$this->Settings["TemplateLocationCacheInterval"]
91 ." MINUTE");
92 }
93 }
100 function GlobalExceptionHandler($Exception)
101 {
102 # display exception info
103 $Location = $Exception->getFile()."[".$Exception->getLine()."]";
104 ?><table width="100%" cellpadding="5"
105 style="border: 2px solid #666666; background: #CCCCCC;
106 font-family: Courier New, Courier, monospace;
107 margin-top: 10px;"><tr><td>
108 <div style="color: #666666;">
109 <span style="font-size: 150%;">
110 <b>Uncaught Exception</b></span><br />
111 <b>Message:</b> <i><?PHP print $Exception->getMessage(); ?></i><br />
112 <b>Location:</b> <i><?PHP print $Location; ?></i><br />
113 <b>Trace:</b>
114 <blockquote><pre><?PHP print $Exception->getTraceAsString();
115 ?></pre></blockquote>
116 </div>
117 </td></tr></table><?PHP
118
119 # log exception if possible
120 $LogMsg = "Uncaught exception (".$Exception->getMessage().").";
121 $this->LogError(self::LOGLVL_ERROR, $LogMsg);
122 }
136 static function AddObjectDirectory(
137 $Dir, $Prefix = "", $ClassPattern = NULL, $ClassReplacement = NULL)
138 {
139 # make sure directory has trailing slash
140 $Dir = $Dir.((substr($Dir, -1) != "/") ? "/" : "");
141
142 # add directory to directory list
143 self::$ObjectDirectories = array_merge(
144 array($Dir => array(
145 "Prefix" => $Prefix,
146 "ClassPattern" => $ClassPattern,
147 "ClassReplacement" => $ClassReplacement,
148 )),
149 self::$ObjectDirectories);
150 }
151
171 function AddImageDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
172 {
173 # add directories to existing image directory list
174 $this->ImageDirList = $this->AddToDirList(
175 $this->ImageDirList, $Dir, $SearchLast, $SkipSlashCheck);
176 }
177
198 function AddIncludeDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
199 {
200 # add directories to existing image directory list
201 $this->IncludeDirList = $this->AddToDirList(
202 $this->IncludeDirList, $Dir, $SearchLast, $SkipSlashCheck);
203 }
204
224 function AddInterfaceDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
225 {
226 # add directories to existing image directory list
227 $this->InterfaceDirList = $this->AddToDirList(
228 $this->InterfaceDirList, $Dir, $SearchLast, $SkipSlashCheck);
229 }
230
250 function AddFunctionDirectories($Dir, $SearchLast = FALSE, $SkipSlashCheck = FALSE)
251 {
252 # add directories to existing image directory list
253 $this->FunctionDirList = $this->AddToDirList(
254 $this->FunctionDirList, $Dir, $SearchLast, $SkipSlashCheck);
255 }
256
262 function SetBrowserDetectionFunc($DetectionFunc)
263 {
264 $this->BrowserDetectFunc = $DetectionFunc;
265 }
266
273 function AddUnbufferedCallback($Callback, $Parameters=array())
274 {
275 if (is_callable($Callback))
276 {
277 $this->UnbufferedCallbacks[] = array($Callback, $Parameters);
278 }
279 }
280
287 function TemplateLocationCacheExpirationInterval($NewInterval = -1)
288 {
289 if ($NewInterval >= 0)
290 {
291 $this->Settings["TemplateLocationCacheInterval"] = $NewInterval;
292 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
293 ." SET TemplateLocationCacheInterval = '"
294 .intval($NewInterval)."'");
295 }
296 return $this->Settings["TemplateLocationCacheInterval"];
297 }
298
304 static function SessionLifetime($NewValue = NULL)
305 {
306 if ($NewValue !== NULL)
307 {
308 self::$SessionLifetime = $NewValue;
309 }
310 return self::$SessionLifetime;
311 }
312
317 function LoadPage($PageName)
318 {
319 # sanitize incoming page name and save local copy
320 $PageName = preg_replace("/[^a-zA-Z0-9_.-]/", "", $PageName);
321 $this->PageName = $PageName;
322
323 # buffer any output from includes or PHP file
324 ob_start();
325
326 # include any files needed to set up execution environment
327 foreach ($this->EnvIncludes as $IncludeFile)
328 {
329 include($IncludeFile);
330 }
331
332 # signal page load
333 $this->SignalEvent("EVENT_PAGE_LOAD", array("PageName" => $PageName));
334
335 # signal PHP file load
336 $SignalResult = $this->SignalEvent("EVENT_PHP_FILE_LOAD", array(
337 "PageName" => $PageName));
338
339 # if signal handler returned new page name value
340 $NewPageName = $PageName;
341 if (($SignalResult["PageName"] != $PageName)
342 && strlen($SignalResult["PageName"]))
343 {
344 # if new page name value is page file
345 if (file_exists($SignalResult["PageName"]))
346 {
347 # use new value for PHP file name
348 $PageFile = $SignalResult["PageName"];
349 }
350 else
351 {
352 # use new value for page name
353 $NewPageName = $SignalResult["PageName"];
354 }
355 }
356
357 # if we do not already have a PHP file
358 if (!isset($PageFile))
359 {
360 # look for PHP file for page
361 $OurPageFile = "pages/".$NewPageName.".php";
362 $LocalPageFile = "local/pages/".$NewPageName.".php";
363 $PageFile = file_exists($LocalPageFile) ? $LocalPageFile
364 : (file_exists($OurPageFile) ? $OurPageFile
365 : "pages/".$this->DefaultPage.".php");
366 }
367
368 # load PHP file
369 include($PageFile);
370
371 # save buffered output to be displayed later after HTML file loads
372 $PageOutput = ob_get_contents();
373 ob_end_clean();
374
375 # signal PHP file load is complete
376 ob_start();
377 $Context["Variables"] = get_defined_vars();
378 $this->SignalEvent("EVENT_PHP_FILE_LOAD_COMPLETE",
379 array("PageName" => $PageName, "Context" => $Context));
380 $PageCompleteOutput = ob_get_contents();
381 ob_end_clean();
382
383 # set up for possible TSR (Terminate and Stay Resident :))
384 $ShouldTSR = $this->PrepForTSR();
385
386 # if PHP file indicated we should autorefresh to somewhere else
387 if ($this->JumpToPage)
388 {
389 if (!strlen(trim($PageOutput)))
390 {
391 ?><html>
392 <head>
393 <meta http-equiv="refresh" content="0; URL=<?PHP
394 print($this->JumpToPage); ?>">
395 </head>
396 <body bgcolor="white">
397 </body>
398 </html><?PHP
399 }
400 }
401 # else if HTML loading is not suppressed
402 elseif (!$this->SuppressHTML)
403 {
404 # set content-type to get rid of diacritic errors
405 header("Content-Type: text/html; charset="
406 .$this->HtmlCharset, TRUE);
407
408 # load common HTML file (defines common functions) if available
409 $CommonHtmlFile = $this->FindFile($this->IncludeDirList,
410 "Common", array("tpl", "html"));
411 if ($CommonHtmlFile) { include($CommonHtmlFile); }
412
413 # load UI functions
414 $this->LoadUIFunctions();
415
416 # begin buffering content
417 ob_start();
418
419 # signal HTML file load
420 $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD", array(
421 "PageName" => $PageName));
422
423 # if signal handler returned new page name value
424 $NewPageName = $PageName;
425 $PageContentFile = NULL;
426 if (($SignalResult["PageName"] != $PageName)
427 && strlen($SignalResult["PageName"]))
428 {
429 # if new page name value is HTML file
430 if (file_exists($SignalResult["PageName"]))
431 {
432 # use new value for HTML file name
433 $PageContentFile = $SignalResult["PageName"];
434 }
435 else
436 {
437 # use new value for page name
438 $NewPageName = $SignalResult["PageName"];
439 }
440 }
441
442 # load page content HTML file if available
443 if ($PageContentFile === NULL)
444 {
445 $PageContentFile = $this->FindFile(
446 $this->InterfaceDirList, $NewPageName,
447 array("tpl", "html"));
448 }
449 if ($PageContentFile)
450 {
451 include($PageContentFile);
452 }
453 else
454 {
455 print "<h2>ERROR: No HTML/TPL template found"
456 ." for this page.</h2>";
457 }
458
459 # signal HTML file load complete
460 $SignalResult = $this->SignalEvent("EVENT_HTML_FILE_LOAD_COMPLETE");
461
462 # stop buffering and save output
463 $PageContentOutput = ob_get_contents();
464 ob_end_clean();
465
466 # load page start HTML file if available
467 ob_start();
468 $PageStartFile = $this->FindFile($this->IncludeDirList, "Start",
469 array("tpl", "html"), array("StdPage", "StandardPage"));
470 if ($PageStartFile) { include($PageStartFile); }
471 $PageStartOutput = ob_get_contents();
472 ob_end_clean();
473
474 # load page end HTML file if available
475 ob_start();
476 $PageEndFile = $this->FindFile($this->IncludeDirList, "End",
477 array("tpl", "html"), array("StdPage", "StandardPage"));
478 if ($PageEndFile) { include($PageEndFile); }
479 $PageEndOutput = ob_get_contents();
480 ob_end_clean();
481
482 # get list of any required files not loaded
483 $RequiredFiles = $this->GetRequiredFilesNotYetLoaded($PageContentFile);
484
485 # if a browser detection function has been made available
486 if (is_callable($this->BrowserDetectFunc))
487 {
488 # call function to get browser list
489 $Browsers = call_user_func($this->BrowserDetectFunc);
490
491 # for each required file
492 $NewRequiredFiles = array();
493 foreach ($RequiredFiles as $File)
494 {
495 # if file name includes browser keyword
496 if (preg_match("/%BROWSER%/", $File))
497 {
498 # for each browser
499 foreach ($Browsers as $Browser)
500 {
501 # substitute in browser name and add to new file list
502 $NewRequiredFiles[] = preg_replace(
503 "/%BROWSER%/", $Browser, $File);
504 }
505 }
506 else
507 {
508 # add to new file list
509 $NewRequiredFiles[] = $File;
510 }
511 }
512 $RequiredFiles = $NewRequiredFiles;
513 }
514
515 # for each required file
516 foreach ($RequiredFiles as $File)
517 {
518 # locate specific file to use
519 $FilePath = $this->GUIFile($File);
520
521 # if file was found
522 if ($FilePath)
523 {
524 # determine file type
525 $NamePieces = explode(".", $File);
526 $FileSuffix = strtolower(array_pop($NamePieces));
527
528 # add file to HTML output based on file type
529 $FilePath = htmlspecialchars($FilePath);
530 switch ($FileSuffix)
531 {
532 case "js":
533 $Tag = '<script type="text/javascript" src="'
534 .$FilePath.'"></script>';
535 $PageEndOutput = preg_replace(
536 "#</body>#i", $Tag."\n</body>", $PageEndOutput, 1);
537 break;
538
539 case "css":
540 $Tag = '<link rel="stylesheet" type="text/css"'
541 .' media="all" href="'.$FilePath.'">';
542 $PageStartOutput = preg_replace(
543 "#</head>#i", $Tag."\n</head>", $PageStartOutput, 1);
544 break;
545 }
546 }
547 }
548
549 # write out page
550 print $PageStartOutput.$PageContentOutput.$PageEndOutput;
551 }
552
553 # run any post-processing routines
554 foreach ($this->PostProcessingFuncs as $Func)
555 {
556 call_user_func_array($Func["FunctionName"], $Func["Arguments"]);
557 }
558
559 # write out any output buffered from page code execution
560 if (strlen($PageOutput))
561 {
562 if (!$this->SuppressHTML)
563 {
564 ?><table width="100%" cellpadding="5"
565 style="border: 2px solid #666666; background: #CCCCCC;
566 font-family: Courier New, Courier, monospace;
567 margin-top: 10px;"><tr><td><?PHP
568 }
569 if ($this->JumpToPage)
570 {
571 ?><div style="color: #666666;"><span style="font-size: 150%;">
572 <b>Page Jump Aborted</b></span>
573 (because of error or other unexpected output)<br />
574 <b>Jump Target:</b>
575 <i><?PHP print($this->JumpToPage); ?></i></div><?PHP
576 }
577 print $PageOutput;
578 if (!$this->SuppressHTML)
579 {
580 ?></td></tr></table><?PHP
581 }
582 }
583
584 # write out any output buffered from the page code execution complete signal
585 if (!$this->JumpToPage && !$this->SuppressHTML && strlen($PageCompleteOutput))
586 {
587 print $PageCompleteOutput;
588 }
589
590 # execute callbacks that should not have their output buffered
591 foreach ($this->UnbufferedCallbacks as $Callback)
592 {
593 call_user_func_array($Callback[0], $Callback[1]);
594 }
595
596 # terminate and stay resident (TSR!) if indicated and HTML has been output
597 # (only TSR if HTML has been output because otherwise browsers will misbehave)
598 if ($ShouldTSR) { $this->LaunchTSR(); }
599 }
600
606 function GetPageName()
607 {
608 return $this->PageName;
609 }
610
617 function SetJumpToPage($Page)
618 {
619 if (!is_null($Page) && (strpos($Page, "?") === FALSE)
620 && ((strpos($Page, "=") !== FALSE)
621 || ((stripos($Page, ".php") === FALSE)
622 && (stripos($Page, ".htm") === FALSE)
623 && (strpos($Page, "/") === FALSE)))
624 && (stripos($Page, "http://") !== 0)
625 && (stripos($Page, "https://") !== 0))
626 {
627 $this->JumpToPage = "index.php?P=".$Page;
628 }
629 else
630 {
631 $this->JumpToPage = $Page;
632 }
633 }
634
639 function JumpToPageIsSet()
640 {
641 return ($this->JumpToPage === NULL) ? FALSE : TRUE;
642 }
643
653 function HtmlCharset($NewSetting = NULL)
654 {
655 if ($NewSetting !== NULL) { $this->HtmlCharset = $NewSetting; }
656 return $this->HtmlCharset;
657 }
658
665 function SuppressHTMLOutput($NewSetting = TRUE)
666 {
667 $this->SuppressHTML = $NewSetting;
668 }
669
676 function ActiveUserInterface($UIName = NULL)
677 {
678 if ($UIName !== NULL)
679 {
680 $this->ActiveUI = preg_replace("/^SPTUI--/", "", $UIName);
681 }
682 return $this->ActiveUI;
683 }
684
690 function GetUserInterfaces()
691 {
692 # possible UI directories
693 $InterfaceDirs = array(
694 "interface",
695 "local/interface");
696
697 # start out with an empty list
698 $Interfaces = array();
699
700 # for each possible UI directory
701 foreach ($InterfaceDirs as $InterfaceDir)
702 {
703 $Dir = dir($InterfaceDir);
704
705 # for each file in current directory
706 while (($DirEntry = $Dir->read()) !== FALSE)
707 {
708 $InterfacePath = $InterfaceDir."/".$DirEntry;
709
710 # skip anything that doesn't have a name in the required format
711 if (!preg_match('/^[a-zA-Z]+$/', $DirEntry))
712 {
713 continue;
714 }
715
716 # skip anything that isn't a directory
717 if (!is_dir($InterfacePath))
718 {
719 continue;
720 }
721
722 # read the UI name (if available)
723 $UIName = @file_get_contents($InterfacePath."/NAME");
724
725 # use the directory name if the UI name isn't available
726 if ($UIName === FALSE || !strlen($UIName))
727 {
728 $UIName = $DirEntry;
729 }
730
731 $Interfaces[$InterfacePath] = $UIName;
732 }
733
734 $Dir->close();
735 }
736
737 # return list to caller
738 return $Interfaces;
739 }
740
756 function AddPostProcessingCall($FunctionName,
757 &$Arg1 = self::NOVALUE, &$Arg2 = self::NOVALUE, &$Arg3 = self::NOVALUE,
758 &$Arg4 = self::NOVALUE, &$Arg5 = self::NOVALUE, &$Arg6 = self::NOVALUE,
759 &$Arg7 = self::NOVALUE, &$Arg8 = self::NOVALUE, &$Arg9 = self::NOVALUE)
760 {
761 $FuncIndex = count($this->PostProcessingFuncs);
762 $this->PostProcessingFuncs[$FuncIndex]["FunctionName"] = $FunctionName;
763 $this->PostProcessingFuncs[$FuncIndex]["Arguments"] = array();
764 $Index = 1;
765 while (isset(${"Arg".$Index}) && (${"Arg".$Index} !== self::NOVALUE))
766 {
767 $this->PostProcessingFuncs[$FuncIndex]["Arguments"][$Index]
768 =& ${"Arg".$Index};
769 $Index++;
770 }
771 }
772
778 function AddEnvInclude($FileName)
779 {
780 $this->EnvIncludes[] = $FileName;
781 }
782
789 function GUIFile($FileName)
790 {
791 # determine which location to search based on file suffix
792 $FileIsImage = preg_match("/\.(gif|jpg|png)$/", $FileName);
793 $DirList = $FileIsImage ? $this->ImageDirList : $this->IncludeDirList;
794
795 # search for file
796 $FoundFileName = $this->FindFile($DirList, $FileName);
797
798 # add non-image files to list of found files (used for required files loading)
799 if (!$FileIsImage) { $this->FoundUIFiles[] = basename($FoundFileName); }
800
801 # return file name to caller
802 return $FoundFileName;
803 }
804
814 function PUIFile($FileName)
815 {
816 $FullFileName = $this->GUIFile($FileName);
817 if ($FullFileName) { print($FullFileName); }
818 }
819
827 function RequireUIFile($FileName)
828 {
829 $this->AdditionalRequiredUIFiles[] = $FileName;
830 }
831
840 function LoadFunction($Callback)
841 {
842 # if specified function is not currently available
843 if (!is_callable($Callback))
844 {
845 # if function info looks legal
846 if (is_string($Callback) && strlen($Callback))
847 {
848 # start with function directory list
849 $Locations = $this->FunctionDirList;
850
851 # add object directories to list
852 $Locations = array_merge(
853 $Locations, array_keys(self::$ObjectDirectories));
854
855 # look for function file
856 $FunctionFileName = $this->FindFile($Locations, "F-".$Callback,
857 array("php", "html"));
858
859 # if function file was found
860 if ($FunctionFileName)
861 {
862 # load function file
863 include_once($FunctionFileName);
864 }
865 else
866 {
867 # log error indicating function load failed
868 $this->LogError(self::LOGLVL_ERROR, "Unable to load function"
869 ." for callback \"".$Callback."\".");
870 }
871 }
872 else
873 {
874 # log error indicating specified function info was bad
875 $this->LogError(self::LOGLVL_ERROR, "Unloadable callback value"
876 ." (".$Callback.")"
877 ." passed to AF::LoadFunction().");
878 }
879 }
880
881 # report to caller whether function load succeeded
882 return is_callable($Callback);
883 }
884
889 function GetElapsedExecutionTime()
890 {
891 return microtime(TRUE) - $this->ExecutionStartTime;
892 }
893
898 function GetSecondsBeforeTimeout()
899 {
900 return ini_get("max_execution_time") - $this->GetElapsedExecutionTime();
901 }
902
907 function HtaccessSupport()
908 {
909 # HTACCESS_SUPPORT is set in the .htaccess file
910 return isset($_SERVER["HTACCESS_SUPPORT"]);
911 }
912
924 function LogError($Level, $Msg)
925 {
926 # if error level is at or below current logging level
927 if ($this->Settings["LoggingLevel"] >= $Level)
928 {
929 # attempt to log error message
930 $Result = $this->LogMessage($Level, $Msg);
931
932 # if logging attempt failed and level indicated significant error
933 if (($Result === FALSE) && ($Level <= self::LOGLVL_ERROR))
934 {
935 # throw exception about inability to log error
936 static $AlreadyThrewException = FALSE;
937 if (!$AlreadyThrewException)
938 {
939 $AlreadyThrewException = TRUE;
940 throw new Exception("Unable to log error (".$Level.": ".$Msg.").");
941 }
942 }
943
944 # report to caller whether message was logged
945 return $Result;
946 }
947 else
948 {
949 # report to caller that message was not logged
950 return FALSE;
951 }
952 }
953
965 function LogMessage($Level, $Msg)
966 {
967 # if message level is at or below current logging level
968 if ($this->Settings["LoggingLevel"] >= $Level)
969 {
970 # attempt to open log file
971 $FHndl = @fopen("local/logs/cwis.log", "a");
972
973 # if log file could not be open
974 if ($FHndl === FALSE)
975 {
976 # report to caller that message was not logged
977 return FALSE;
978 }
979 else
980 {
981 # format log entry
982 $ErrorAbbrevs = array(
983 self::LOGLVL_FATAL => "FTL",
984 self::LOGLVL_ERROR => "ERR",
985 self::LOGLVL_WARNING => "WRN",
986 self::LOGLVL_INFO => "INF",
987 self::LOGLVL_DEBUG => "DBG",
988 self::LOGLVL_TRACE => "TRC",
989 );
990 $LogEntry = date("Y-m-d H:i:s")
991 ." ".($this->RunningInBackground ? "B" : "F")
992 ." ".$ErrorAbbrevs[$Level]
993 ." ".$Msg;
994
995 # write entry to log
996 $Success = fputs($FHndl, $LogEntry."\n");
997
998 # close log file
999 fclose($FHndl);
1000
1001 # report to caller whether message was logged
1002 return ($Success === FALSE) ? FALSE : TRUE;
1003 }
1004 }
1005 else
1006 {
1007 # report to caller that message was not logged
1008 return FALSE;
1009 }
1010 }
1011
1033 function LoggingLevel($NewValue = NULL)
1034 {
1035 # if new logging level was specified
1036 if ($NewValue !== NULL)
1037 {
1038 # constrain new level to within legal bounds and store locally
1039 $this->Settings["LoggingLevel"] = max(min($NewValue, 6), 1);
1040
1041 # save new logging level in database
1042 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
1043 ." SET LoggingLevel = "
1044 .intval($this->Settings["LoggingLevel"]));
1045 }
1046
1047 # report current logging level to caller
1048 return $this->Settings["LoggingLevel"];
1049 }
1050
1055 const LOGLVL_TRACE = 6;
1060 const LOGLVL_DEBUG = 5;
1066 const LOGLVL_INFO = 4;
1071 const LOGLVL_WARNING = 3;
1077 const LOGLVL_ERROR = 2;
1082 const LOGLVL_FATAL = 1;
1083
1084 /*@)*/ /* Application Framework */
1085
1086 # ---- Event Handling ----------------------------------------------------
1087 /*@(*/
1089
1093 const EVENTTYPE_DEFAULT = 1;
1099 const EVENTTYPE_CHAIN = 2;
1105 const EVENTTYPE_FIRST = 3;
1113 const EVENTTYPE_NAMED = 4;
1114
1116 const ORDER_FIRST = 1;
1118 const ORDER_MIDDLE = 2;
1120 const ORDER_LAST = 3;
1121
1130 function RegisterEvent($EventsOrEventName, $EventType = NULL)
1131 {
1132 # convert parameters to array if not already in that form
1133 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1134 : array($EventsOrEventName => $Type);
1135
1136 # for each event
1137 foreach ($Events as $Name => $Type)
1138 {
1139 # store event information
1140 $this->RegisteredEvents[$Name]["Type"] = $Type;
1141 $this->RegisteredEvents[$Name]["Hooks"] = array();
1142 }
1143 }
1144
1150 function IsRegisteredEvent($EventName)
1151 {
1152 return array_key_exists($EventName, $this->RegisteredEvents)
1153 ? TRUE : FALSE;
1154 }
1155
1169 function HookEvent($EventsOrEventName, $Callback = NULL, $Order = self::ORDER_MIDDLE)
1170 {
1171 # convert parameters to array if not already in that form
1172 $Events = is_array($EventsOrEventName) ? $EventsOrEventName
1173 : array($EventsOrEventName => $Callback);
1174
1175 # for each event
1176 $Success = TRUE;
1177 foreach ($Events as $EventName => $EventCallback)
1178 {
1179 # if callback is valid
1180 if (is_callable($EventCallback))
1181 {
1182 # if this is a periodic event we process internally
1183 if (isset($this->PeriodicEvents[$EventName]))
1184 {
1185 # process event now
1186 $this->ProcessPeriodicEvent($EventName, $EventCallback);
1187 }
1188 # if specified event has been registered
1189 elseif (isset($this->RegisteredEvents[$EventName]))
1190 {
1191 # add callback for event
1192 $this->RegisteredEvents[$EventName]["Hooks"][]
1193 = array("Callback" => $EventCallback, "Order" => $Order);
1194
1195 # sort callbacks by order
1196 if (count($this->RegisteredEvents[$EventName]["Hooks"]) > 1)
1197 {
1198 usort($this->RegisteredEvents[$EventName]["Hooks"],
1199 array("ApplicationFramework", "HookEvent_OrderCompare"));
1200 }
1201 }
1202 else
1203 {
1204 $Success = FALSE;
1205 }
1206 }
1207 else
1208 {
1209 $Success = FALSE;
1210 }
1211 }
1212
1213 # report to caller whether all callbacks were hooked
1214 return $Success;
1215 }
1216 private static function HookEvent_OrderCompare($A, $B)
1217 {
1218 if ($A["Order"] == $B["Order"]) { return 0; }
1219 return ($A["Order"] < $B["Order"]) ? -1 : 1;
1220 }
1221
1230 function SignalEvent($EventName, $Parameters = NULL)
1231 {
1232 $ReturnValue = NULL;
1233
1234 # if event has been registered
1235 if (isset($this->RegisteredEvents[$EventName]))
1236 {
1237 # set up default return value (if not NULL)
1238 switch ($this->RegisteredEvents[$EventName]["Type"])
1239 {
1240 case self::EVENTTYPE_CHAIN:
1241 $ReturnValue = $Parameters;
1242 break;
1243
1244 case self::EVENTTYPE_NAMED:
1245 $ReturnValue = array();
1246 break;
1247 }
1248
1249 # for each callback for this event
1250 foreach ($this->RegisteredEvents[$EventName]["Hooks"] as $Hook)
1251 {
1252 # invoke callback
1253 $Callback = $Hook["Callback"];
1254 $Result = ($Parameters !== NULL)
1255 ? call_user_func_array($Callback, $Parameters)
1256 : call_user_func($Callback);
1257
1258 # process return value based on event type
1259 switch ($this->RegisteredEvents[$EventName]["Type"])
1260 {
1261 case self::EVENTTYPE_CHAIN:
1262 if ($Result !== NULL)
1263 {
1264 foreach ($Parameters as $Index => $Value)
1265 {
1266 if (array_key_exists($Index, $Result))
1267 {
1268 $Parameters[$Index] = $Result[$Index];
1269 }
1270 }
1271 $ReturnValue = $Parameters;
1272 }
1273 break;
1274
1275 case self::EVENTTYPE_FIRST:
1276 if ($Result !== NULL)
1277 {
1278 $ReturnValue = $Result;
1279 break 2;
1280 }
1281 break;
1282
1283 case self::EVENTTYPE_NAMED:
1284 $CallbackName = is_array($Callback)
1285 ? (is_object($Callback[0])
1286 ? get_class($Callback[0])
1287 : $Callback[0])."::".$Callback[1]
1288 : $Callback;
1289 $ReturnValue[$CallbackName] = $Result;
1290 break;
1291
1292 default:
1293 break;
1294 }
1295 }
1296 }
1297 else
1298 {
1299 $this->LogError(self::LOGLVL_WARNING,
1300 "Unregistered event signaled (".$EventName.").");
1301 }
1302
1303 # return value if any to caller
1304 return $ReturnValue;
1305 }
1306
1312 function IsStaticOnlyEvent($EventName)
1313 {
1314 return isset($this->PeriodicEvents[$EventName]) ? TRUE : FALSE;
1315 }
1316
1317 /*@)*/ /* Event Handling */
1318
1319 # ---- Task Management ---------------------------------------------------
1320 /*@(*/
1322
1324 const PRIORITY_HIGH = 1;
1326 const PRIORITY_MEDIUM = 2;
1328 const PRIORITY_LOW = 3;
1330 const PRIORITY_BACKGROUND = 4;
1331
1344 function QueueTask($Callback, $Parameters = NULL,
1345 $Priority = self::PRIORITY_MEDIUM, $Description = "")
1346 {
1347 # pack task info and write to database
1348 if ($Parameters === NULL) { $Parameters = array(); }
1349 $this->DB->Query("INSERT INTO TaskQueue"
1350 ." (Callback, Parameters, Priority, Description)"
1351 ." VALUES ('".addslashes(serialize($Callback))."', '"
1352 .addslashes(serialize($Parameters))."', ".intval($Priority).", '"
1353 .addslashes($Description)."')");
1354 }
1355
1373 function QueueUniqueTask($Callback, $Parameters = NULL,
1374 $Priority = self::PRIORITY_MEDIUM, $Description = "")
1375 {
1376 if ($this->TaskIsInQueue($Callback, $Parameters))
1377 {
1378 $QueryResult = $this->DB->Query("SELECT TaskId,Priority FROM TaskQueue"
1379 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
1380 .($Parameters ? " AND Parameters = '"
1381 .addslashes(serialize($Parameters))."'" : ""));
1382 if ($QueryResult !== FALSE)
1383 {
1384 $Record = $this->DB->FetchRow();
1385 if ($Record["Priority"] > $Priority)
1386 {
1387 $this->DB->Query("UPDATE TaskQueue"
1388 ." SET Priority = ".intval($Priority)
1389 ." WHERE TaskId = ".intval($Record["TaskId"]));
1390 }
1391 }
1392 return FALSE;
1393 }
1394 else
1395 {
1396 $this->QueueTask($Callback, $Parameters, $Priority, $Description);
1397 return TRUE;
1398 }
1399 }
1400
1410 function TaskIsInQueue($Callback, $Parameters = NULL)
1411 {
1412 $FoundCount = $this->DB->Query("SELECT COUNT(*) AS FoundCount FROM TaskQueue"
1413 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
1414 .($Parameters ? " AND Parameters = '"
1415 .addslashes(serialize($Parameters))."'" : ""),
1416 "FoundCount")
1417 + $this->DB->Query("SELECT COUNT(*) AS FoundCount FROM RunningTasks"
1418 ." WHERE Callback = '".addslashes(serialize($Callback))."'"
1419 .($Parameters ? " AND Parameters = '"
1420 .addslashes(serialize($Parameters))."'" : ""),
1421 "FoundCount");
1422 return ($FoundCount ? TRUE : FALSE);
1423 }
1424
1430 function GetTaskQueueSize($Priority = NULL)
1431 {
1432 return $this->DB->Query("SELECT COUNT(*) AS QueueSize FROM TaskQueue"
1433 .($Priority ? " WHERE Priority = ".intval($Priority) : ""),
1434 "QueueSize");
1435 }
1436
1444 function GetQueuedTaskList($Count = 100, $Offset = 0)
1445 {
1446 return $this->GetTaskList("SELECT * FROM TaskQueue"
1447 ." ORDER BY Priority, TaskId ", $Count, $Offset);
1448 }
1449
1457 function GetRunningTaskList($Count = 100, $Offset = 0)
1458 {
1459 return $this->GetTaskList("SELECT * FROM RunningTasks"
1460 ." WHERE StartedAt >= '".date("Y-m-d H:i:s",
1461 (time() - ini_get("max_execution_time")))."'"
1462 ." ORDER BY StartedAt", $Count, $Offset);
1463 }
1464
1472 function GetOrphanedTaskList($Count = 100, $Offset = 0)
1473 {
1474 return $this->GetTaskList("SELECT * FROM RunningTasks"
1475 ." WHERE StartedAt < '".date("Y-m-d H:i:s",
1476 (time() - ini_get("max_execution_time")))."'"
1477 ." ORDER BY StartedAt", $Count, $Offset);
1478 }
1479
1484 function GetOrphanedTaskCount()
1485 {
1486 return $this->DB->Query("SELECT COUNT(*) AS Count FROM RunningTasks"
1487 ." WHERE StartedAt < '".date("Y-m-d H:i:s",
1488 (time() - ini_get("max_execution_time")))."'",
1489 "Count");
1490 }
1491
1497 function ReQueueOrphanedTask($TaskId, $NewPriority = NULL)
1498 {
1499 $this->DB->Query("LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
1500 $this->DB->Query("INSERT INTO TaskQueue"
1501 ." (Callback,Parameters,Priority,Description) "
1502 ."SELECT Callback, Parameters, Priority, Description"
1503 ." FROM RunningTasks WHERE TaskId = ".intval($TaskId));
1504 if ($NewPriority !== NULL)
1505 {
1506 $NewTaskId = $this->DB->LastInsertId("TaskQueue");
1507 $this->DB->Query("UPDATE TaskQueue SET Priority = "
1508 .intval($NewPriority)
1509 ." WHERE TaskId = ".intval($NewTaskId));
1510 }
1511 $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
1512 $this->DB->Query("UNLOCK TABLES");
1513 }
1514
1519 function DeleteTask($TaskId)
1520 {
1521 $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
1522 $this->DB->Query("DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
1523 }
1524
1532 function GetTask($TaskId)
1533 {
1534 # assume task will not be found
1535 $Task = NULL;
1536
1537 # look for task in task queue
1538 $this->DB->Query("SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
1539
1540 # if task was not found in queue
1541 if (!$this->DB->NumRowsSelected())
1542 {
1543 # look for task in running task list
1544 $this->DB->Query("SELECT * FROM RunningTasks WHERE TaskId = "
1545 .intval($TaskId));
1546 }
1547
1548 # if task was found
1549 if ($this->DB->NumRowsSelected())
1550 {
1551 # if task was periodic
1552 $Row = $this->DB->FetchRow();
1553 if ($Row["Callback"] ==
1554 serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
1555 {
1556 # unpack periodic task callback
1557 $WrappedCallback = unserialize($Row["Parameters"]);
1558 $Task["Callback"] = $WrappedCallback[1];
1559 $Task["Parameters"] = $WrappedCallback[2];
1560 }
1561 else
1562 {
1563 # unpack task callback and parameters
1564 $Task["Callback"] = unserialize($Row["Callback"]);
1565 $Task["Parameters"] = unserialize($Row["Parameters"]);
1566 }
1567 }
1568
1569 # return task to caller
1570 return $Task;
1571 }
1572
1580 function TaskExecutionEnabled($NewValue = NULL)
1581 {
1582 if ($NewValue !== NULL)
1583 {
1584 $this->Settings["TaskExecutionEnabled"] = $NewValue ? 1 : 0;
1585 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
1586 ." SET TaskExecutionEnabled = "
1587 .$this->Settings["TaskExecutionEnabled"]);
1588 }
1589 return $this->Settings["TaskExecutionEnabled"];
1590 }
1591
1597 function MaxTasks($NewValue = NULL)
1598 {
1599 if (func_num_args() && ($NewValue >= 1))
1600 {
1601 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
1602 ." SET MaxTasksRunning = ".intval($NewValue));
1603 $this->Settings["MaxTasksRunning"] = intval($NewValue);
1604 }
1605 return $this->Settings["MaxTasksRunning"];
1606 }
1607
1615 function MaxExecutionTime($NewValue = NULL)
1616 {
1617 if (func_num_args() && !ini_get("safe_mode"))
1618 {
1619 if ($NewValue != $this->Settings["MaxExecTime"])
1620 {
1621 $this->Settings["MaxExecTime"] = max($NewValue, 5);
1622 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
1623 ." SET MaxExecTime = '"
1624 .intval($this->Settings["MaxExecTime"])."'");
1625 }
1626 ini_set("max_execution_time", $this->Settings["MaxExecTime"]);
1627 set_time_limit($this->Settings["MaxExecTime"]);
1628 }
1629 return ini_get("max_execution_time");
1630 }
1631
1632 /*@)*/ /* Task Management */
1633
1634 # ---- Backward Compatibility --------------------------------------------
1635
1640 function FindCommonTemplate($BaseName)
1641 {
1642 return $this->FindFile(
1643 $this->IncludeDirList, $BaseName, array("tpl", "html"));
1644 }
1645 /*@(*/
1647
1648
1649 # ---- PRIVATE INTERFACE -------------------------------------------------
1650
1651 private $ActiveUI = "default";
1652 private $BrowserDetectFunc;
1653 private $DB;
1654 private $DefaultPage = "Home";
1655 private $EnvIncludes = array();
1656 private $ExecutionStartTime;
1657 private $FoundUIFiles = array();
1658 private $AdditionalRequiredUIFiles = array();
1659 private $HtmlCharset = "UTF-8";
1660 private $JumpToPage = NULL;
1661 private $PageName;
1662 private $MaxRunningTasksToTrack = 250;
1663 private $PostProcessingFuncs = array();
1664 private $RunningInBackground = FALSE;
1665 private $RunningTask;
1666 private $Settings;
1667 private $SuppressHTML = FALSE;
1668 private $SaveTemplateLocationCache = FALSE;
1669 private $UnbufferedCallbacks = array();
1670
1671 private static $AppName = "ScoutAF";
1672 private static $ObjectDirectories = array();
1673 private static $SessionLifetime = 1440;
1674
1679 private $NoTSR = FALSE;
1680
1681 private $PeriodicEvents = array(
1682 "EVENT_HOURLY" => self::EVENTTYPE_DEFAULT,
1683 "EVENT_DAILY" => self::EVENTTYPE_DEFAULT,
1684 "EVENT_WEEKLY" => self::EVENTTYPE_DEFAULT,
1685 "EVENT_MONTHLY" => self::EVENTTYPE_DEFAULT,
1686 "EVENT_PERIODIC" => self::EVENTTYPE_NAMED,
1687 );
1688 private $UIEvents = array(
1689 "EVENT_PAGE_LOAD" => self::EVENTTYPE_DEFAULT,
1690 "EVENT_PHP_FILE_LOAD" => self::EVENTTYPE_CHAIN,
1691 "EVENT_PHP_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
1692 "EVENT_HTML_FILE_LOAD" => self::EVENTTYPE_CHAIN,
1693 "EVENT_HTML_FILE_LOAD_COMPLETE" => self::EVENTTYPE_DEFAULT,
1694 );
1695
1699 private function LoadSettings()
1700 {
1701 # read settings in from database
1702 $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
1703 $this->Settings = $this->DB->FetchRow();
1704
1705 # if settings were not previously initialized
1706 if (!$this->Settings)
1707 {
1708 # initialize settings in database
1709 $this->DB->Query("INSERT INTO ApplicationFrameworkSettings"
1710 ." (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
1711
1712 # read new settings in from database
1713 $this->DB->Query("SELECT * FROM ApplicationFrameworkSettings");
1714 $this->Settings = $this->DB->FetchRow();
1715 }
1716
1717 # if template location cache has been saved to database
1718 if (isset($this->Settings["TemplateLocationCache"]))
1719 {
1720 # unserialize cache values into array and use if valid
1721 $Cache = unserialize($this->Settings["TemplateLocationCache"]);
1722 $this->Settings["TemplateLocationCache"] =
1723 count($Cache) ? $Cache : array();
1724 }
1725 else
1726 {
1727 # start with empty cache
1728 $this->Settings["TemplateLocationCache"] = array();
1729 }
1730 }
1731
1748 private function FindFile($DirectoryList, $BaseName,
1749 $PossibleSuffixes = NULL, $PossiblePrefixes = NULL)
1750 {
1751 # generate template cache index for this page
1752 $CacheIndex = md5(serialize($DirectoryList))
1753 .":".$this->ActiveUI.":".$BaseName;
1754
1755 # if we have cached location and cache expiration time has not elapsed
1756 if (($this->Settings["TemplateLocationCacheInterval"] > 0)
1757 && count($this->Settings["TemplateLocationCache"])
1758 && array_key_exists($CacheIndex,
1759 $this->Settings["TemplateLocationCache"])
1760 && (time() < strtotime(
1761 $this->Settings["TemplateLocationCacheExpiration"])))
1762 {
1763 # use template location from cache
1764 $FoundFileName = $this->Settings[
1765 "TemplateLocationCache"][$CacheIndex];
1766 }
1767 else
1768 {
1769 # if suffixes specified and base name does not include suffix
1770 if (count($PossibleSuffixes)
1771 && !preg_match("/\.[a-zA-Z0-9]+$/", $BaseName))
1772 {
1773 # add versions of file names with suffixes to file name list
1774 $FileNames = array();
1775 foreach ($PossibleSuffixes as $Suffix)
1776 {
1777 $FileNames[] = $BaseName.".".$Suffix;
1778 }
1779 }
1780 else
1781 {
1782 # use base name as file name
1783 $FileNames = array($BaseName);
1784 }
1785
1786 # if prefixes specified
1787 if (count($PossiblePrefixes))
1788 {
1789 # add versions of file names with prefixes to file name list
1790 $NewFileNames = array();
1791 foreach ($FileNames as $FileName)
1792 {
1793 foreach ($PossiblePrefixes as $Prefix)
1794 {
1795 $NewFileNames[] = $Prefix.$FileName;
1796 }
1797 }
1798 $FileNames = $NewFileNames;
1799 }
1800
1801 # for each possible location
1802 $FoundFileName = NULL;
1803 foreach ($DirectoryList as $Dir)
1804 {
1805 # substitute active UI name into path
1806 $Dir = str_replace("%ACTIVEUI%", $this->ActiveUI, $Dir);
1807
1808 # for each possible file name
1809 foreach ($FileNames as $File)
1810 {
1811 # if template is found at location
1812 if (file_exists($Dir.$File))
1813 {
1814 # save full template file name and stop looking
1815 $FoundFileName = $Dir.$File;
1816 break 2;
1817 }
1818 }
1819 }
1820
1821 # save location in cache
1822 $this->Settings["TemplateLocationCache"][$CacheIndex]
1823 = $FoundFileName;
1824
1825 # set flag indicating that cache should be saved
1826 $this->SaveTemplateLocationCache = TRUE;
1827 }
1828
1829 # return full template file name to caller
1830 return $FoundFileName;
1831 }
1832
1839 private function GetRequiredFilesNotYetLoaded($PageContentFile)
1840 {
1841 # start out assuming no files required
1842 $RequiredFiles = array();
1843
1844 # if page content file supplied
1845 if ($PageContentFile)
1846 {
1847 # if file containing list of required files is available
1848 $Path = dirname($PageContentFile);
1849 $RequireListFile = $Path."/REQUIRES";
1850 if (file_exists($RequireListFile))
1851 {
1852 # read in list of required files
1853 $RequestedFiles = file($RequireListFile);
1854
1855 # for each line in required file list
1856 foreach ($RequestedFiles as $Line)
1857 {
1858 # if line is not a comment
1859 $Line = trim($Line);
1860 if (!preg_match("/^#/", $Line))
1861 {
1862 # if file has not already been loaded
1863 if (!in_array($Line, $this->FoundUIFiles))
1864 {
1865 # add to list of required files
1866 $RequiredFiles[] = $Line;
1867 }
1868 }
1869 }
1870 }
1871 }
1872
1873 # add in additional required files if any
1874 if (count($this->AdditionalRequiredUIFiles))
1875 {
1876 # make sure there are no duplicates
1877 $AdditionalRequiredUIFiles = array_unique(
1878 $this->AdditionalRequiredUIFiles);
1879
1880 $RequiredFiles = array_merge(
1881 $RequiredFiles, $AdditionalRequiredUIFiles);
1882 }
1883
1884 # return list of required files to caller
1885 return $RequiredFiles;
1886 }
1887
1891 private function SetUpObjectAutoloading()
1892 {
1893 function __autoload($ClassName)
1894 {
1895 ApplicationFramework::AutoloadObjects($ClassName);
1896 }
1897 }
1898
1904 static function AutoloadObjects($ClassName)
1905 {
1906 static $FileLists;
1907 foreach (self::$ObjectDirectories as $Location => $Info)
1908 {
1909 if (is_dir($Location))
1910 {
1911 $NewClassName = ($Info["ClassPattern"] && $Info["ClassReplacement"])
1912 ? preg_replace($Info["ClassPattern"],
1913 $Info["ClassReplacement"], $ClassName)
1914 : $ClassName;
1915 if (!isset($FileLists[$Location]))
1916 {
1917 $FileLists[$Location] = self::ReadDirectoryTree(
1918 $Location, '/^.+\.php$/i');
1919 }
1920 $FileNames = $FileLists[$Location];
1921 $TargetName = strtolower($Info["Prefix"].$NewClassName.".php");
1922 foreach ($FileNames as $FileName)
1923 {
1924 if (strtolower($FileName) == $TargetName)
1925 {
1926 require_once($Location.$FileName);
1927 break 2;
1928 }
1929 }
1930 }
1931 }
1932 }
1933
1941 private static function ReadDirectoryTree($Directory, $Pattern)
1942 {
1943 $CurrentDir = getcwd();
1944 chdir($Directory);
1945 $DirIter = new RecursiveDirectoryIterator(".");
1946 $IterIter = new RecursiveIteratorIterator($DirIter);
1947 $RegexResults = new RegexIterator($IterIter, $Pattern,
1948 RecursiveRegexIterator::GET_MATCH);
1949 $FileList = array();
1950 foreach ($RegexResults as $Result)
1951 {
1952 $FileList[] = substr($Result[0], 2);
1953 }
1954 chdir($CurrentDir);
1955 return $FileList;
1956 }
1963 private function LoadUIFunctions()
1964 {
1965 $Dirs = array(
1966 "local/interface/%ACTIVEUI%/include",
1967 "interface/%ACTIVEUI%/include",
1968 "local/interface/default/include",
1969 "interface/default/include",
1970 );
1971 foreach ($Dirs as $Dir)
1972 {
1973 $Dir = str_replace("%ACTIVEUI%", $this->ActiveUI, $Dir);
1974 if (is_dir($Dir))
1975 {
1976 $FileNames = scandir($Dir);
1977 foreach ($FileNames as $FileName)
1978 {
1979 if (preg_match("/^F-([A-Za-z_]+)\.php/", $FileName, $Matches)
1980 || preg_match("/^F-([A-Za-z_]+)\.html/", $FileName, $Matches))
1981 {
1982 if (!function_exists($Matches[1]))
1983 {
1984 include_once($Dir."/".$FileName);
1985 }
1986 }
1987 }
1988 }
1989 }
1990 }
1991
1997 private function ProcessPeriodicEvent($EventName, $Callback)
1998 {
1999 # retrieve last execution time for event if available
2000 $Signature = self::GetCallbackSignature($Callback);
2001 $LastRun = $this->DB->Query("SELECT LastRunAt FROM PeriodicEvents"
2002 ." WHERE Signature = '".addslashes($Signature)."'", "LastRunAt");
2003
2004 # determine whether enough time has passed for event to execute
2005 $EventPeriods = array(
2006 "EVENT_HOURLY" => 60*60,
2007 "EVENT_DAILY" => 60*60*24,
2008 "EVENT_WEEKLY" => 60*60*24*7,
2009 "EVENT_MONTHLY" => 60*60*24*30,
2010 "EVENT_PERIODIC" => 0,
2011 );
2012 $ShouldExecute = (($LastRun === NULL)
2013 || (time() > (strtotime($LastRun) + $EventPeriods[$EventName])))
2014 ? TRUE : FALSE;
2015
2016 # if event should run
2017 if ($ShouldExecute)
2018 {
2019 # add event to task queue
2020 $WrapperCallback = array("ApplicationFramework", "PeriodicEventWrapper");
2021 $WrapperParameters = array(
2022 $EventName, $Callback, array("LastRunAt" => $LastRun));
2023 $this->QueueUniqueTask($WrapperCallback, $WrapperParameters);
2024 }
2025 }
2026
2034 private static function PeriodicEventWrapper($EventName, $Callback, $Parameters)
2035 {
2036 static $DB;
2037 if (!isset($DB)) { $DB = new Database(); }
2038
2039 # run event
2040 $ReturnVal = call_user_func_array($Callback, $Parameters);
2041
2042 # if event is already in database
2043 $Signature = self::GetCallbackSignature($Callback);
2044 if ($DB->Query("SELECT COUNT(*) AS EventCount FROM PeriodicEvents"
2045 ." WHERE Signature = '".addslashes($Signature)."'", "EventCount"))
2046 {
2047 # update last run time for event
2048 $DB->Query("UPDATE PeriodicEvents SET LastRunAt = "
2049 .(($EventName == "EVENT_PERIODIC")
2050 ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
2051 : "NOW()")
2052 ." WHERE Signature = '".addslashes($Signature)."'");
2053 }
2054 else
2055 {
2056 # add last run time for event to database
2057 $DB->Query("INSERT INTO PeriodicEvents (Signature, LastRunAt) VALUES "
2058 ."('".addslashes($Signature)."', "
2059 .(($EventName == "EVENT_PERIODIC")
2060 ? "'".date("Y-m-d H:i:s", time() + ($ReturnVal * 60))."'"
2061 : "NOW()").")");
2062 }
2063 }
2064
2070 private static function GetCallbackSignature($Callback)
2071 {
2072 return !is_array($Callback) ? $Callback
2073 : (is_object($Callback[0]) ? md5(serialize($Callback[0])) : $Callback[0])
2074 ."::".$Callback[1];
2075 }
2076
2081 private function PrepForTSR()
2082 {
2083 # if HTML has been output and it's time to launch another task
2084 # (only TSR if HTML has been output because otherwise browsers
2085 # may misbehave after connection is closed)
2086 if (($this->JumpToPage || !$this->SuppressHTML)
2087 && (time() > (strtotime($this->Settings["LastTaskRunAt"])
2088 + (ini_get("max_execution_time")
2089 / $this->Settings["MaxTasksRunning"]) + 5))
2090 && $this->GetTaskQueueSize()
2091 && $this->Settings["TaskExecutionEnabled"])
2092 {
2093 # begin buffering output for TSR
2094 ob_start();
2095
2096 # let caller know it is time to launch another task
2097 return TRUE;
2098 }
2099 else
2100 {
2101 # let caller know it is not time to launch another task
2102 return FALSE;
2103 }
2104 }
2105
2110 private function LaunchTSR()
2111 {
2112 # set headers to close out connection to browser
2113 if (!$this->NoTSR)
2114 {
2115 ignore_user_abort(TRUE);
2116 header("Connection: close");
2117 header("Content-Length: ".ob_get_length());
2118 }
2119
2120 # output buffered content
2121 ob_end_flush();
2122 flush();
2123
2124 # write out any outstanding data and end HTTP session
2125 session_write_close();
2126
2127 # set flag indicating that we are now running in background
2128 $this->RunningInBackground = TRUE;
2129
2130 # if there is still a task in the queue
2131 if ($this->GetTaskQueueSize())
2132 {
2133 # turn on output buffering to (hopefully) record any crash output
2134 ob_start();
2135
2136 # lock tables and grab last task run time to double check
2137 $this->DB->Query("LOCK TABLES ApplicationFrameworkSettings WRITE");
2138 $this->LoadSettings();
2139
2140 # if still time to launch another task
2141 if (time() > (strtotime($this->Settings["LastTaskRunAt"])
2142 + (ini_get("max_execution_time")
2143 / $this->Settings["MaxTasksRunning"]) + 5))
2144 {
2145 # update the "last run" time and release tables
2146 $this->DB->Query("UPDATE ApplicationFrameworkSettings"
2147 ." SET LastTaskRunAt = '".date("Y-m-d H:i:s")."'");
2148 $this->DB->Query("UNLOCK TABLES");
2149
2150 # run tasks while there is a task in the queue and enough time left
2151 do
2152 {
2153 # run the next task
2154 $this->RunNextTask();
2155 }
2156 while ($this->GetTaskQueueSize()
2157 && ($this->GetSecondsBeforeTimeout() > 65));
2158 }
2159 else
2160 {
2161 # release tables
2162 $this->DB->Query("UNLOCK TABLES");
2163 }
2164 }
2165 }
2166
2174 private function GetTaskList($DBQuery, $Count, $Offset)
2175 {
2176 $this->DB->Query($DBQuery." LIMIT ".intval($Offset).",".intval($Count));
2177 $Tasks = array();
2178 while ($Row = $this->DB->FetchRow())
2179 {
2180 $Tasks[$Row["TaskId"]] = $Row;
2181 if ($Row["Callback"] ==
2182 serialize(array("ApplicationFramework", "PeriodicEventWrapper")))
2183 {
2184 $WrappedCallback = unserialize($Row["Parameters"]);
2185 $Tasks[$Row["TaskId"]]["Callback"] = $WrappedCallback[1];
2186 $Tasks[$Row["TaskId"]]["Parameters"] = NULL;
2187 }
2188 else
2189 {
2190 $Tasks[$Row["TaskId"]]["Callback"] = unserialize($Row["Callback"]);
2191 $Tasks[$Row["TaskId"]]["Parameters"] = unserialize($Row["Parameters"]);
2192 }
2193 }
2194 return $Tasks;
2195 }
2196
2200 private function RunNextTask()
2201 {
2202 # look for task at head of queue
2203 $this->DB->Query("SELECT * FROM TaskQueue ORDER BY Priority, TaskId LIMIT 1");
2204 $Task = $this->DB->FetchRow();
2205
2206 # if there was a task available
2207 if ($Task)
2208 {
2209 # move task from queue to running tasks list
2210 $this->DB->Query("INSERT INTO RunningTasks "
2211 ."(TaskId,Callback,Parameters,Priority,Description) "
2212 ."SELECT * FROM TaskQueue WHERE TaskId = "
2213 .intval($Task["TaskId"]));
2214 $this->DB->Query("DELETE FROM TaskQueue WHERE TaskId = "
2215 .intval($Task["TaskId"]));
2216
2217 # unpack stored task info
2218 $Callback = unserialize($Task["Callback"]);
2219 $Parameters = unserialize($Task["Parameters"]);
2220
2221 # attempt to load task callback if not already available
2222 $this->LoadFunction($Callback);
2223
2224 # run task
2225 $this->RunningTask = $Task;
2226 if ($Parameters)
2227 {
2228 call_user_func_array($Callback, $Parameters);
2229 }
2230 else
2231 {
2232 call_user_func($Callback);
2233 }
2234 unset($this->RunningTask);
2235
2236 # remove task from running tasks list
2237 $this->DB->Query("DELETE FROM RunningTasks"
2238 ." WHERE TaskId = ".intval($Task["TaskId"]));
2239
2240 # prune running tasks list if necessary
2241 $RunningTasksCount = $this->DB->Query(
2242 "SELECT COUNT(*) AS TaskCount FROM RunningTasks", "TaskCount");
2243 if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
2244 {
2245 $this->DB->Query("DELETE FROM RunningTasks ORDER BY StartedAt"
2246 ." LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
2247 }
2248 }
2249 }
2250
2256 function OnCrash()
2257 {
2258 if (isset($this->RunningTask))
2259 {
2260 if (function_exists("error_get_last"))
2261 {
2262 $CrashInfo["LastError"] = error_get_last();
2263 }
2264 if (ob_get_length() !== FALSE)
2265 {
2266 $CrashInfo["OutputBuffer"] = ob_get_contents();
2267 }
2268 if (isset($CrashInfo))
2269 {
2270 $DB = new Database();
2271 $DB->Query("UPDATE RunningTasks SET CrashInfo = '"
2272 .addslashes(serialize($CrashInfo))
2273 ."' WHERE TaskId = ".intval($this->RunningTask["TaskId"]));
2274 }
2275 }
2276
2277 print("\n");
2278 return;
2279
2280 if (ob_get_length() !== FALSE)
2281 {
2282 ?>
2283 <table width="100%" cellpadding="5" style="border: 2px solid #666666; background: #FFCCCC; font-family: Courier New, Courier, monospace; margin-top: 10px; font-weight: bold;"><tr><td>
2284 <div style="font-size: 200%;">CRASH OUTPUT</div><?PHP
2285 ob_end_flush();
2286 ?></td></tr></table><?PHP
2287 }
2288 }
2289
2306 private function AddToDirList($DirList, $Dir, $SearchLast, $SkipSlashCheck)
2307 {
2308 # convert incoming directory to array of directories (if needed)
2309 $Dirs = is_array($Dir) ? $Dir : array($Dir);
2310
2311 # reverse array so directories are searched in specified order
2312 $Dirs = array_reverse($Dirs);
2313
2314 # for each directory
2315 foreach ($Dirs as $Location)
2316 {
2317 # make sure directory includes trailing slash
2318 if (!$SkipSlashCheck)
2319 {
2320 $Location = $Location
2321 .((substr($Location, -1) != "/") ? "/" : "");
2322 }
2323
2324 # remove directory from list if already present
2325 if (in_array($Location, $DirList))
2326 {
2327 $DirList = array_diff(
2328 $DirList, array($Location));
2329 }
2330
2331 # add directory to list of directories
2332 if ($SearchLast)
2333 {
2334 array_push($DirList, $Location);
2335 }
2336 else
2337 {
2338 array_unshift($DirList, $Location);
2339 }
2340 }
2341
2342 # return updated directory list to caller
2343 return $DirList;
2344 }
2345
2347 private $InterfaceDirList = array(
2348 "local/interface/%ACTIVEUI%/",
2349 "interface/%ACTIVEUI%/",
2350 "local/interface/default/",
2351 "interface/default/",
2352 );
2357 private $IncludeDirList = array(
2358 "local/interface/%ACTIVEUI%/include/",
2359 "interface/%ACTIVEUI%/include/",
2360 "local/interface/default/include/",
2361 "interface/default/include/",
2362 );
2364 private $ImageDirList = array(
2365 "local/interface/%ACTIVEUI%/images/",
2366 "interface/%ACTIVEUI%/images/",
2367 "local/interface/default/images/",
2368 "interface/default/images/",
2369 );
2371 private $FunctionDirList = array(
2372 "local/interface/%ACTIVEUI%/include/",
2373 "interface/%ACTIVEUI%/include/",
2374 "local/interface/default/include/",
2375 "interface/default/include/",
2376 "local/include/",
2377 "include/",
2378 );
2379
2380 const NOVALUE = ".-+-.NO VALUE PASSED IN FOR ARGUMENT.-+-.";
2381 };
2382
2383
2384 ?>