CWIS Developer Documentation
PluginManager.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: PluginManager.php
4 #
5 # Part of the ScoutLib application support library
6 # Copyright 2009-2013 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu
8 #
9 
14 {
15 
16  # ---- PUBLIC INTERFACE --------------------------------------------------
17 
25  public function __construct($AppFramework, $PluginDirectories)
26  {
27  # save framework and directory list for later use
28  $this->AF = $AppFramework;
29  Plugin::SetApplicationFramework($AppFramework);
30  $this->DirsToSearch = $PluginDirectories;
31 
32  # get our own database handle
33  $this->DB = new Database();
34 
35  # hook into events to load plugin PHP and HTML files
36  $this->AF->HookEvent("EVENT_PHP_FILE_LOAD", array($this, "FindPluginPhpFile"),
37  ApplicationFramework::ORDER_LAST);
38  $this->AF->HookEvent("EVENT_HTML_FILE_LOAD", array($this, "FindPluginHtmlFile"),
39  ApplicationFramework::ORDER_LAST);
40 
41  # tell PluginCaller helper object how to get to us
42  PluginCaller::$Manager = $this;
43  }
44 
49  public function LoadPlugins()
50  {
51  $ErrMsgs = array();
52 
53  # look for plugin files
54  $PluginFiles = $this->FindPlugins($this->DirsToSearch);
55 
56  # for each plugin found
57  foreach ($PluginFiles as $PluginName => $PluginFileName)
58  {
59  # attempt to load plugin
60  $Result = $this->LoadPlugin($PluginName, $PluginFileName);
61 
62  # if errors were encountered during loading
63  if (is_array($Result))
64  {
65  # save errors
66  $ErrMsgs[$PluginName][] = $Result;
67  }
68  else
69  {
70  # add plugin to list of loaded plugins
71  $this->Plugins[$PluginName] = $Result;
72  }
73  }
74 
75  # check dependencies and drop any plugins with failed dependencies
76  $DepErrMsgs = $this->CheckDependencies($this->Plugins);
77  $DisabledPlugins = array();
78  foreach ($DepErrMsgs as $PluginName => $Msgs)
79  {
80  $DisabledPlugins[] = $PluginName;
81  foreach ($Msgs as $Msg)
82  {
83  $ErrMsgs[$PluginName][] = $Msg;
84  }
85  }
86 
87  # sort plugins according to any loading order requests
88  $this->Plugins = $this->SortPluginsByInitializationPrecedence(
89  $this->Plugins);
90 
91  # for each plugin
92  foreach ($this->Plugins as $PluginName => $Plugin)
93  {
94  # if plugin is loaded and enabled
95  if (!in_array($PluginName, $DisabledPlugins)
96  && $Plugin->IsEnabled())
97  {
98  # attempt to make plugin ready
99  $Result = $this->ReadyPlugin($Plugin);
100 
101  # if making plugin ready failed
102  if ($Result !== NULL)
103  {
104  # save error messages
105  foreach ($Result as $Msg)
106  {
107  $ErrMsgs[$PluginName][] = $Msg;
108  }
109  }
110  else
111  {
112  # mark plugin as ready
113  $Plugin->IsReady(TRUE);
114  }
115  }
116  }
117 
118  # check plugin dependencies again in case an install or upgrade failed
119  $DepErrMsgs = $this->CheckDependencies($this->Plugins, TRUE);
120  foreach ($DepErrMsgs as $PluginName => $Msgs)
121  {
122  $this->Plugins[$PluginName]->IsReady(FALSE);
123  foreach ($Msgs as $Msg)
124  {
125  $ErrMsgs[$PluginName][] = $Msg;
126  }
127  }
128 
129  # save plugin files names and any error messages for later use
130  $this->PluginFiles = $PluginFiles;
131  $this->ErrMsgs = $ErrMsgs;
132 
133  # report to caller whether any problems were encountered
134  return count($ErrMsgs) ? FALSE : TRUE;
135  }
136 
142  public function GetErrorMessages()
143  {
144  return $this->ErrMsgs;
145  }
146 
155  public function GetPlugin($PluginName, $EvenIfNotReady = FALSE)
156  {
157  if (!$EvenIfNotReady && array_key_exists($PluginName, $this->Plugins)
158  && !$this->Plugins[$PluginName]->IsReady())
159  {
160  $Trace = debug_backtrace();
161  $Caller = basename($Trace[0]["file"]).":".$Trace[0]["line"];
162  throw new Exception("Attempt to access uninitialized plugin "
163  .$PluginName." from ".$Caller);
164  }
165  return isset($this->Plugins[$PluginName])
166  ? $this->Plugins[$PluginName] : NULL;
167  }
168 
173  public function GetPlugins()
174  {
175  return $this->Plugins;
176  }
177 
185  public function GetPluginForCurrentPage()
186  {
187  return $this->GetPlugin($this->PageFilePlugin);
188  }
189 
195  public function GetPluginAttributes()
196  {
197  # for each loaded plugin
198  $Info = array();
199  foreach ($this->Plugins as $PluginName => $Plugin)
200  {
201  # retrieve plugin attributes
202  $Info[$PluginName] = $Plugin->GetAttributes();
203 
204  # add in other values to attributes
205  $Info[$PluginName]["Enabled"] = $Plugin->IsEnabled();
206  $Info[$PluginName]["Installed"] = $Plugin->IsInstalled();
207  $Info[$PluginName]["ClassFile"] = $this->PluginFiles[$PluginName];
208  }
209 
210  # sort plugins by name
211  uasort($Info, function ($A, $B) {
212  $AName = strtoupper($A["Name"]);
213  $BName = strtoupper($B["Name"]);
214  return ($AName == $BName) ? 0
215  : ($AName < $BName) ? -1 : 1;
216  });
217 
218  # return plugin info to caller
219  return $Info;
220  }
221 
227  public function GetDependents($PluginName)
228  {
229  $Dependents = array();
230  $AllAttribs = $this->GetPluginAttributes();
231  foreach ($AllAttribs as $Name => $Attribs)
232  {
233  if (array_key_exists($PluginName, $Attribs["Requires"]))
234  {
235  $Dependents[] = $Name;
236  $SubDependents = $this->GetDependents($Name);
237  $Dependents = array_merge($Dependents, $SubDependents);
238  }
239  }
240  return $Dependents;
241  }
242 
247  public function GetActivePluginList()
248  {
249  $ActivePluginNames = array();
250  foreach ($this->Plugins as $PluginName => $Plugin)
251  {
252  if ($Plugin->IsReady())
253  {
254  $ActivePluginNames[] = $PluginName;
255  }
256  }
257  return $ActivePluginNames;
258  }
259 
266  public function PluginEnabled($PluginName, $NewValue = NULL)
267  {
268  return !isset($this->Plugins[$PluginName]) ? FALSE
269  : $this->Plugins[$PluginName]->IsEnabled($NewValue);
270  }
271 
277  public function UninstallPlugin($PluginName)
278  {
279  # assume success
280  $Result = NULL;
281 
282  # if plugin is installed
283  if ($this->Plugins[$PluginName]->IsInstalled())
284  {
285  # call uninstall method for plugin
286  $Result = $this->Plugins[$PluginName]->Uninstall();
287 
288  # if plugin uninstall method succeeded
289  if ($Result === NULL)
290  {
291  # remove plugin info from database
292  $this->DB->Query("DELETE FROM PluginInfo"
293  ." WHERE BaseName = '".addslashes($PluginName)."'");
294 
295  # drop our data for the plugin
296  unset($this->Plugins[$PluginName]);
297  unset($this->PluginFiles[$PluginName]);
298  }
299  }
300 
301  # report results (if any) to caller
302  return $Result;
303  }
304 
305 
306  # ---- PRIVATE INTERFACE -------------------------------------------------
307 
308  private $AF;
309  private $DB;
310  private $DirsToSearch;
311  private $ErrMsgs = array();
312  private $PageFilePlugin = NULL;
313  private $Plugins = array();
314  private $PluginFiles = array();
315  private $PluginHasDir = array();
316 
324  private function FindPlugins($DirsToSearch)
325  {
326  # for each directory
327  $PluginFiles = array();
328  foreach ($DirsToSearch as $Dir)
329  {
330  # if directory exists
331  if (is_dir($Dir))
332  {
333  # for each file in directory
334  $FileNames = scandir($Dir);
335  foreach ($FileNames as $FileName)
336  {
337  # if file looks like base plugin file
338  if (preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*\.php$/", $FileName))
339  {
340  # add file to list
341  $PluginName = preg_replace("/\.php$/", "", $FileName);
342  $PluginFiles[$PluginName] = $Dir."/".$FileName;
343  }
344  # else if file looks like plugin directory
345  elseif (is_dir($Dir."/".$FileName)
346  && preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*/", $FileName))
347  {
348  # if there is a base plugin file in the directory
349  $PluginName = $FileName;
350  $PluginFile = $Dir."/".$PluginName."/".$PluginName.".php";
351  if (file_exists($PluginFile))
352  {
353  # add file to list
354  $PluginFiles[$PluginName] = $PluginFile;
355  }
356  else
357  {
358  # record error
359  $this->ErrMsgs[$PluginName][] =
360  "Expected plugin file <i>".$PluginName.".php</i> not"
361  ." found in plugin subdirectory <i>"
362  .$Dir."/".$PluginName."</i>";
363  }
364  }
365  }
366  }
367  }
368 
369  # return info about found plugins to caller
370  return $PluginFiles;
371  }
372 
380  private function LoadPlugin($PluginName, $PluginFileName)
381  {
382  # bring in plugin class file
383  include_once($PluginFileName);
384 
385  # if plugin class was not defined by file
386  if (!class_exists($PluginName))
387  {
388  $ErrMsgs[] = "Expected class <i>".$PluginName
389  ."</i> not found in plugin file <i>"
390  .$PluginFileName."</i>";
391  }
392  else
393  {
394  # if plugin class is not a valid descendant of base plugin class
395  if (!is_subclass_of($PluginName, "Plugin"))
396  {
397  $ErrMsgs[] = "Plugin <b>".$PluginName."</b>"
398  ." could not be loaded because <i>".$PluginName."</i> class"
399  ." was not a subclass of base <i>Plugin</i> class";
400  }
401  else
402  {
403  # instantiate and register the plugin
404  $Plugin = new $PluginName($PluginName);
405 
406  # check required plugin attributes
407  $RequiredAttribs = array("Name", "Version");
408  $Attribs = $Plugin->GetAttributes();
409  foreach ($RequiredAttribs as $AttribName)
410  {
411  if (!strlen($Attribs[$AttribName]))
412  {
413  $ErrMsgs[] = "Plugin <b>".$PluginName."</b>"
414  ." could not be loaded because it"
415  ." did not have a <i>"
416  .$AttribName."</i> attribute set.";
417  }
418  }
419 
420  # if all required attributes were found
421  if (!isset($ErrMsgs))
422  {
423  # if plugin has its own subdirectory
424  $this->PluginHasDir[$PluginName] = preg_match(
425  "%/".$PluginName."/".$PluginName.".php\$%",
426  $PluginFileName) ? TRUE : FALSE;
427  if ($this->PluginHasDir[$PluginName])
428  {
429  # if plugin has its own object directory
430  $Dir = dirname($PluginFileName);
431  if (is_dir($Dir."/objects"))
432  {
433  # add object directory to class autoloading list
434  ApplicationFramework::AddObjectDirectory($Dir."/objects");
435  }
436  else
437  {
438  # add plugin directory to class autoloading list
439  ApplicationFramework::AddObjectDirectory($Dir);
440  }
441 
442  # if plugin has its own interface directory
443  $InterfaceDir = $Dir."/interface";
444  if (is_dir($InterfaceDir))
445  {
446  # scan contents of interface directory for
447  # entries other than the default default
448  $InterfaceSubdirsFound = FALSE;
449  foreach (scandir($InterfaceDir) as $Entry)
450  {
451  if (($Entry != "default") && ($Entry[0] != "."))
452  {
453  $InterfaceSubdirsFound = TRUE;
454  break;
455  }
456  }
457 
458  # if entries found other than the default default
459  if ($InterfaceSubdirsFound)
460  {
461  # add directory to those scanned for interfaces
462  $this->AF->AddInterfaceDirectories(
463  array($InterfaceDir."/%ACTIVEUI%/",
464  $InterfaceDir."/%DEFAULTUI%/"));
465  }
466 
467  # add plugin interface object directories if present
468  $ActiveUI = $this->AF->ActiveUserInterface();
469  $DefaultUI = $this->AF->DefaultUserInterface();
470  if (is_dir($InterfaceDir."/".$ActiveUI."/objects"))
471  {
472  ApplicationFramework::AddObjectDirectory(
473  $InterfaceDir."/%ACTIVEUI%/objects");
474  }
475  if (is_dir($InterfaceDir."/".$DefaultUI."/objects"))
476  {
477  ApplicationFramework::AddObjectDirectory(
478  $InterfaceDir."/%DEFAULTUI%/objects");
479  }
480 
481  # add plugin interface include directories if present
482  if (is_dir($InterfaceDir."/".$DefaultUI."/include"))
483  {
484  $this->AF->AddIncludeDirectories(
485  $InterfaceDir."/%DEFAULTUI%/include");
486  }
487  if (is_dir($InterfaceDir."/".$ActiveUI."/include"))
488  {
489  $this->AF->AddIncludeDirectories(
490  $InterfaceDir."/%ACTIVEUI%/include");
491  }
492  }
493  }
494  }
495  }
496  }
497 
498  # return loaded plugin or error messages, as appropriate
499  return isset($ErrMsgs) ? $ErrMsgs : $Plugin;
500  }
501 
507  private function ReadyPlugin(&$Plugin)
508  {
509  # install or upgrade plugin if needed
510  $PluginInstalled = $this->InstallPlugin($Plugin);
511 
512  # if install/upgrade failed
513  if (is_string($PluginInstalled))
514  {
515  # report errors to caller
516  return array($PluginInstalled);
517  }
518 
519  # set up plugin configuration options
520  $ErrMsgs = $Plugin->SetUpConfigOptions();
521 
522  # if plugin configuration setup failed
523  if ($ErrMsgs !== NULL)
524  {
525  # report errors to caller
526  return is_array($ErrMsgs) ? $ErrMsgs : array($ErrMsgs);
527  }
528 
529  # set default configuration values if necessary
530  if ($PluginInstalled)
531  {
532  $this->SetPluginDefaultConfigValues($Plugin);
533  }
534 
535  # initialize the plugin
536  $ErrMsgs = $Plugin->Initialize();
537 
538  # if initialization failed
539  if ($ErrMsgs !== NULL)
540  {
541  # report errors to caller
542  return is_array($ErrMsgs) ? $ErrMsgs : array($ErrMsgs);
543  }
544 
545  # register any events declared by plugin
546  $Events = $Plugin->DeclareEvents();
547  if (count($Events)) { $this->AF->RegisterEvent($Events); }
548 
549  # if plugin has events that need to be hooked
550  $EventsToHook = $Plugin->HookEvents();
551  if (count($EventsToHook))
552  {
553  # for each event
554  $ErrMsgs = array();
555  foreach ($EventsToHook as $EventName => $PluginMethods)
556  {
557  # for each method to hook for the event
558  if (!is_array($PluginMethods))
559  { $PluginMethods = array($PluginMethods); }
560  foreach ($PluginMethods as $PluginMethod)
561  {
562  # if the event only allows static callbacks
563  if ($this->AF->IsStaticOnlyEvent($EventName))
564  {
565  # hook event with shell for static callback
566  $Caller = new PluginCaller(
567  $Plugin->GetBaseName(), $PluginMethod);
568  $Result = $this->AF->HookEvent(
569  $EventName,
570  array($Caller, "CallPluginMethod"));
571  }
572  else
573  {
574  # hook event
575  $Result = $this->AF->HookEvent(
576  $EventName, array($Plugin, $PluginMethod));
577  }
578 
579  # record any errors
580  if ($Result === FALSE)
581  {
582  $ErrMsgs[] = "Unable to hook requested event <i>"
583  .$EventName."</i> for plugin <b>"
584  .$Plugin->GetBaseName()."</b>";
585  }
586  }
587  }
588 
589  # if event hook setup failed
590  if (count($ErrMsgs))
591  {
592  # report errors to caller
593  return $ErrMsgs;
594  }
595  }
596 
597  # report success to caller
598  return NULL;
599  }
600 
608  private function InstallPlugin(&$Plugin)
609  {
610  # if plugin has not been installed
611  $InstallOrUpgradePerformed = FALSE;
612  $PluginName = $Plugin->GetBaseName();
613  $Attribs = $Plugin->GetAttributes();
614  if (!$Plugin->IsInstalled())
615  {
616  # set default values if present
617  $this->SetPluginDefaultConfigValues($Plugin, TRUE);
618 
619  # install plugin
620  $ErrMsg = $Plugin->Install();
621  $InstallOrUpgradePerformed = TRUE;
622 
623  # if install succeeded
624  if ($ErrMsg == NULL)
625  {
626  # mark plugin as installed
627  $Plugin->IsInstalled(TRUE);
628  }
629  else
630  {
631  # return error message about installation failure
632  return "Installation of plugin <b>"
633  .$PluginName."</b> failed: <i>".$ErrMsg."</i>";
634  }
635  }
636  else
637  {
638  # if plugin version is newer than version in database
639  if (version_compare($Attribs["Version"],
640  $Plugin->InstalledVersion()) == 1)
641  {
642  # set default values for any new configuration settings
643  $this->SetPluginDefaultConfigValues($Plugin);
644 
645  # upgrade plugin
646  $ErrMsg = $Plugin->Upgrade($Plugin->InstalledVersion());
647  $InstallOrUpgradePerformed = TRUE;
648 
649  # if upgrade succeeded
650  if ($ErrMsg === NULL)
651  {
652  # update plugin version in database
653  $Plugin->InstalledVersion($Attribs["Version"]);
654  }
655  else
656  {
657  # report error message about upgrade failure
658  return "Upgrade of plugin <b>"
659  .$PluginName."</b> from version <i>"
660  .addslashes($Plugin->InstalledVersion())
661  ."</i> to version <i>"
662  .addslashes($Attribs["Version"])."</i> failed: <i>"
663  .$ErrMsg."</i>";
664  }
665  }
666  # else if plugin version is older than version in database
667  elseif (version_compare($Attribs["Version"],
668  $Plugin->InstalledVersion()) == -1)
669  {
670  # return error message about version conflict
671  return "Plugin <b>"
672  .$PluginName."</b> is older (<i>"
673  .addslashes($Attribs["Version"])
674  ."</i>) than previously-installed version (<i>"
675  .addslashes($Plugin->InstalledVersion())
676  ."</i>).";
677  }
678  }
679 
680  # report result to caller
681  return $InstallOrUpgradePerformed;
682  }
683 
691  private function SetPluginDefaultConfigValues($Plugin, $Overwrite = FALSE)
692  {
693  # if plugin has configuration info
694  $Attribs = $Plugin->GetAttributes();
695  if (isset($Attribs["CfgSetup"]))
696  {
697  foreach ($Attribs["CfgSetup"] as $CfgValName => $CfgSetup)
698  {
699  if (isset($CfgSetup["Default"]) && ($Overwrite
700  || ($Plugin->ConfigSetting($CfgValName) === NULL)))
701  {
702  $Plugin->ConfigSetting($CfgValName, $CfgSetup["Default"]);
703  }
704  }
705  }
706  }
707 
715  private function CheckDependencies($Plugins, $CheckReady = FALSE)
716  {
717  # look until all enabled plugins check out okay
718  $ErrMsgs = array();
719  do
720  {
721  # start out assuming all plugins are okay
722  $AllOkay = TRUE;
723 
724  # for each plugin
725  foreach ($Plugins as $PluginName => $Plugin)
726  {
727  # if plugin is enabled and not checking for ready
728  # or plugin is ready
729  if ($Plugin->IsEnabled() && (!$CheckReady || $Plugin->IsReady()))
730  {
731  # load plugin attributes
732  if (!isset($Attribs[$PluginName]))
733  {
734  $Attribs[$PluginName] = $Plugin->GetAttributes();
735  }
736 
737  # for each dependency for this plugin
738  foreach ($Attribs[$PluginName]["Requires"]
739  as $ReqName => $ReqVersion)
740  {
741  # handle PHP version requirements
742  if ($ReqName == "PHP")
743  {
744  if (version_compare($ReqVersion, phpversion(), ">"))
745  {
746  $ErrMsgs[$PluginName][] = "PHP version "
747  ."<i>".$ReqVersion."</i>"
748  ." required by <b>".$PluginName."</b>"
749  ." was not available. (Current PHP version"
750  ." is <i>".phpversion()."</i>.)";
751  }
752  }
753  # handle PHP extension requirements
754  elseif (preg_match("/^PHPX_/", $ReqName))
755  {
756  list($Dummy, $ExtensionName) = explode("_", $ReqName, 2);
757  if (!extension_loaded($ExtensionName))
758  {
759  $ErrMsgs[$PluginName][] = "PHP extension "
760  ."<i>".$ExtensionName."</i>"
761  ." required by <b>".$PluginName."</b>"
762  ." was not available.";
763  }
764  }
765  # handle dependencies on other plugins
766  else
767  {
768  # load plugin attributes if not already loaded
769  if (isset($Plugins[$ReqName])
770  && !isset($Attribs[$ReqName]))
771  {
772  $Attribs[$ReqName] =
773  $Plugins[$ReqName]->GetAttributes();
774  }
775 
776  # if target plugin is not present or is too old
777  # or is not enabled
778  # or (if appropriate) is not ready
779  if (!isset($Plugins[$ReqName])
780  || version_compare($ReqVersion,
781  $Attribs[$ReqName]["Version"], ">")
782  || !$Plugins[$ReqName]->IsEnabled()
783  || ($CheckReady
784  && !$Plugins[$ReqName]->IsReady()))
785  {
786  # add error message
787  $ErrMsgs[$PluginName][] = "Plugin <i>"
788  .$ReqName." ".$ReqVersion."</i>"
789  ." required by <b>".$PluginName."</b>"
790  ." was not available.";
791  }
792  }
793 
794  # if problem was found with plugin
795  if (isset($ErrMsgs[$PluginName]))
796  {
797  # remove plugin from our list
798  unset($Plugins[$PluginName]);
799 
800  # set flag to indicate a plugin had to be dropped
801  $AllOkay = FALSE;
802  }
803  }
804  }
805  }
806  } while ($AllOkay == FALSE);
807 
808  # return messages about any dropped plugins back to caller
809  return $ErrMsgs;
810  }
811 
820  private function SortPluginsByInitializationPrecedence($Plugins)
821  {
822  # load plugin attributes
823  foreach ($Plugins as $PluginName => $Plugin)
824  {
825  $PluginAttribs[$PluginName] = $Plugin->GetAttributes();
826  }
827 
828  # determine initialization order
829  $PluginsAfterUs = array();
830  foreach ($PluginAttribs as $PluginName => $Attribs)
831  {
832  foreach ($Attribs["InitializeBefore"] as $OtherPluginName)
833  {
834  $PluginsAfterUs[$PluginName][] = $OtherPluginName;
835  }
836  foreach ($Attribs["InitializeAfter"] as $OtherPluginName)
837  {
838  $PluginsAfterUs[$OtherPluginName][] = $PluginName;
839  }
840  }
841 
842  # infer other initialization order cues from lists of required plugins
843  foreach ($PluginAttribs as $PluginName => $Attribs)
844  {
845  # for each required plugin
846  foreach ($Attribs["Requires"]
847  as $RequiredPluginName => $RequiredPluginVersion)
848  {
849  # if there is not a requirement in the opposite direction
850  if (!array_key_exists($PluginName,
851  $PluginAttribs[$RequiredPluginName]["Requires"]))
852  {
853  # if the required plugin is not scheduled to be after us
854  if (!array_key_exists($PluginName, $PluginsAfterUs)
855  || !in_array($RequiredPluginName,
856  $PluginsAfterUs[$PluginName]))
857  {
858  # if we are not already scheduled to be after the required plugin
859  if (!array_key_exists($PluginName, $PluginsAfterUs)
860  || !in_array($RequiredPluginName,
861  $PluginsAfterUs[$PluginName]))
862  {
863  # schedule us to be after the required plugin
864  $PluginsAfterUs[$RequiredPluginName][] =
865  $PluginName;
866  }
867  }
868  }
869  }
870  }
871 
872  # keep track of those plugins we have yet to do and those that are done
873  $UnsortedPlugins = array_keys($Plugins);
874  $PluginsProcessed = array();
875 
876  # limit the number of iterations of the plugin ordering loop
877  # to 10 times the number of plugins we have
878  $MaxIterations = 10 * count($UnsortedPlugins);
879  $IterationCount = 0;
880 
881  # iterate through all the plugins that need processing
882  while (($NextPlugin = array_shift($UnsortedPlugins)) !== NULL)
883  {
884  # check to be sure that we're not looping forever
885  $IterationCount++;
886  if ($IterationCount > $MaxIterations)
887  {
888  throw new Exception(
889  "Max iteration count exceeded trying to determine plugin"
890  ." loading order. Is there a dependency loop?");
891  }
892 
893  # if no plugins require this one, it can go last
894  if (!isset($PluginsAfterUs[$NextPlugin]))
895  {
896  $PluginsProcessed[$NextPlugin] = $MaxIterations;
897  }
898  else
899  {
900  # for plugins that are required by others
901  $Index = $MaxIterations;
902  foreach ($PluginsAfterUs[$NextPlugin] as $GoBefore)
903  {
904  if (!isset($PluginsProcessed[$GoBefore]))
905  {
906  # if there is something that requires us which hasn't
907  # yet been assigned an order, then we can't determine
908  # our own place on this iteration
909  array_push($UnsortedPlugins, $NextPlugin);
910  continue 2;
911  }
912  else
913  {
914  # otherwise, make sure that we're loaded
915  # before the earliest of the things that require us
916  $Index = min($Index, $PluginsProcessed[$GoBefore] - 1);
917  }
918  }
919  $PluginsProcessed[$NextPlugin] = $Index;
920  }
921  }
922 
923  # arrange plugins according to our ordering
924  asort($PluginsProcessed, SORT_NUMERIC);
925  $SortedPlugins = array();
926  foreach ($PluginsProcessed as $PluginName => $SortOrder)
927  {
928  $SortedPlugins[$PluginName] = $Plugins[$PluginName];
929  }
930 
931  # return sorted list to caller
932  return $SortedPlugins;
933  }
934 
943  public function FindPluginPhpFile($PageName)
944  {
945  # build list of possible locations for file
946  $Locations = array(
947  "local/plugins/%PLUGIN%/pages/%PAGE%.php",
948  "plugins/%PLUGIN%/pages/%PAGE%.php",
949  "local/plugins/%PLUGIN%/%PAGE%.php",
950  "plugins/%PLUGIN%/%PAGE%.php",
951  );
952 
953  # look for file and return (possibly) updated page to caller
954  return $this->FindPluginPageFile($PageName, $Locations);
955  }
966  public function FindPluginHtmlFile($PageName)
967  {
968  # build list of possible locations for file
969  $Locations = array(
970  "local/plugins/%PLUGIN%/interface/%ACTIVEUI%/%PAGE%.html",
971  "plugins/%PLUGIN%/interface/%ACTIVEUI%/%PAGE%.html",
972  "local/plugins/%PLUGIN%/interface/%DEFAULTUI%/%PAGE%.html",
973  "plugins/%PLUGIN%/interface/%DEFAULTUI%/%PAGE%.html",
974  "local/plugins/%PLUGIN%/%PAGE%.html",
975  "plugins/%PLUGIN%/%PAGE%.html",
976  );
977 
978  # find HTML file
979  $Params = $this->FindPluginPageFile($PageName, $Locations);
980 
981  # if plugin HTML file was found
982  if ($Params["PageName"] != $PageName)
983  {
984  # add subdirectories for plugin to search paths
985  $Dir = preg_replace("%^local/%", "", dirname($Params["PageName"]));
986  $this->AF->AddImageDirectories(array(
987  "local/".$Dir."/images",
988  $Dir."/images",
989  ));
990  $this->AF->AddIncludeDirectories(array(
991  "local/".$Dir."/include",
992  $Dir."/include",
993  ));
994  $this->AF->AddFunctionDirectories(array(
995  "local/".$Dir."/include",
996  $Dir."/include",
997  ));
998  }
999 
1000  # return possibly revised HTML file name to caller
1001  return $Params;
1002  }
1015  private function FindPluginPageFile($PageName, $Locations)
1016  {
1017  # set up return value assuming we will not find plugin page file
1018  $ReturnValue["PageName"] = $PageName;
1019 
1020  # look for plugin name and plugin page name in base page name
1021  preg_match("/P_([A-Za-z].[A-Za-z0-9]*)_([A-Za-z0-9_-]+)/", $PageName, $Matches);
1022 
1023  # if plugin name and plugin page name were found and plugin name is valid
1024  if (count($Matches) == 3)
1025  {
1026  # if plugin is valid and enabled and has its own subdirectory
1027  $PluginName = $Matches[1];
1028  if (isset($this->Plugins[$PluginName])
1029  && $this->PluginHasDir[$PluginName]
1030  && $this->Plugins[$PluginName]->IsEnabled())
1031  {
1032  # for each possible location
1033  $PageName = $Matches[2];
1034  $ActiveUI = $this->AF->ActiveUserInterface();
1035  $DefaultUI = $this->AF->DefaultUserInterface();
1036  foreach ($Locations as $Loc)
1037  {
1038  # make any needed substitutions into path
1039  $FileName = str_replace(
1040  array("%DEFAULTUI%", "%ACTIVEUI%", "%PLUGIN%", "%PAGE%"),
1041  array($DefaultUI, $ActiveUI, $PluginName, $PageName),
1042  $Loc);
1043 
1044  # if file exists in this location
1045  if (file_exists($FileName))
1046  {
1047  # set return value to contain full plugin page file name
1048  $ReturnValue["PageName"] = $FileName;
1049 
1050  # save plugin name as home of current page
1051  $this->PageFilePlugin = $PluginName;
1052 
1053  # set G_Plugin to plugin associated with current page
1054  $GLOBALS["G_Plugin"] = $this->GetPluginForCurrentPage();
1055 
1056  # stop looking
1057  break;
1058  }
1059  }
1060  }
1061  }
1062 
1063  # return array containing page name or page file name to caller
1064  return $ReturnValue;
1065  }
1066 }
1067 
1079 class PluginCaller
1080 {
1081 
1088  public function __construct($PluginName, $MethodName)
1089  {
1090  $this->PluginName = $PluginName;
1091  $this->MethodName = $MethodName;
1092  }
1093 
1099  public function CallPluginMethod()
1100  {
1101  $Args = func_get_args();
1102  $Plugin = self::$Manager->GetPlugin($this->PluginName);
1103  return call_user_func_array(array($Plugin, $this->MethodName), $Args);
1104  }
1105 
1110  public function GetCallbackAsText()
1111  {
1112  return $this->PluginName."::".$this->MethodName;
1113  }
1114 
1120  public function __sleep()
1121  {
1122  return array("PluginName", "MethodName");
1123  }
1124 
1126  static public $Manager;
1127 
1128  private $PluginName;
1129  private $MethodName;
1130 }
1133 ?>
GetErrorMessages()
Retrieve any error messages generated during plugin loading.
Manager to load and invoke plugins.
GetPlugin($PluginName, $EvenIfNotReady=FALSE)
Retrieve specified plugin.
SQL database abstraction object with smart query caching.
Definition: Database.php:22
UninstallPlugin($PluginName)
Uninstall plugin and (optionally) delete any associated data.
GetDependents($PluginName)
Returns a list of plugins dependent on the specified plugin.
GetActivePluginList()
Get list of active (i.e.
GetPluginAttributes()
Retrieve info about currently loaded plugins.
PluginEnabled($PluginName, $NewValue=NULL)
Get/set whether specified plugin is enabled.
__construct($AppFramework, $PluginDirectories)
PluginManager class constructor.
GetPluginForCurrentPage()
Retrieve plugin for current page (if any).
GetPlugins()
Retrieve all loaded plugins.
static SetApplicationFramework($AF)
Set the application framework to be referenced within plugins.
Definition: Plugin.php:378
LoadPlugins()
Load and initialize plugins.