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  # ---- PUBLIC INTERFACE --------------------------------------------------
16 
24  function __construct($AppFramework, $PluginDirectories)
25  {
26  # save framework and directory list for later use
27  $this->AF = $AppFramework;
28  $this->DirsToSearch = $PluginDirectories;
29 
30  # get our own database handle
31  $this->DB = new Database();
32 
33  # hook into events to load plugin PHP and HTML files
34  $this->AF->HookEvent("EVENT_PHP_FILE_LOAD", array($this, "FindPluginPhpFile"),
35  ApplicationFramework::ORDER_LAST);
36  $this->AF->HookEvent("EVENT_HTML_FILE_LOAD", array($this, "FindPluginHtmlFile"),
37  ApplicationFramework::ORDER_LAST);
38 
39  # tell PluginCaller helper object how to get to us
40  PluginCaller::$Manager = $this;
41  }
42 
53  function LoadPlugins($ForcePluginConfigOptLoad = FALSE)
54  {
55  # clear any existing errors/status
56  $this->ErrMsgs = array();
57  $this->StatusMsgs = array();
58 
59  # load plugin info from database
60  $this->DB->Query("SELECT * FROM PluginInfo");
61  while ($Row = $this->DB->FetchRow())
62  {
63  $this->PluginInfo[$Row["BaseName"]] = $Row;
64  $this->PluginEnabled[$Row["BaseName"]] =
65  $Row["Enabled"] ? TRUE : FALSE;
66  }
67 
68  # load list of all base plugin files
69  $this->FindPlugins($this->DirsToSearch);
70 
71  # for each plugin found
72  foreach ($this->PluginNames as $PluginName)
73  {
74  # bring in plugin class file
75  include_once($this->PluginFiles[$PluginName]);
76 
77  # if plugin class was defined by file
78  if (class_exists($PluginName))
79  {
80  # if plugin class is a valid descendant of base plugin class
81  $Plugin = new $PluginName;
82  if (is_subclass_of($Plugin, "Plugin"))
83  {
84  # set hooks needed for plugin to access plugin manager services
85  $Plugin->SetCfgSaveCallback(array(__CLASS__, "CfgSaveCallback"));
86 
87  # register the plugin
88  $this->Plugins[$PluginName] = $Plugin;
89  $this->Plugins[$PluginName]->Register();
90  if (!isset($this->PluginEnabled[$PluginName]))
91  {
92  $this->PluginEnabled[$PluginName] = FALSE;
93  }
94 
95  # add plugin to database if not already present
96  $Attribs[$PluginName] = $this->Plugins[$PluginName]->GetAttributes();
97  if (!isset($this->PluginInfo[$PluginName]))
98  {
99  $this->DB->Query("INSERT INTO PluginInfo"
100  ." (BaseName, Version, Installed, Enabled)"
101  ." VALUES ('".addslashes($PluginName)."', "
102  ." '".addslashes(
103  $Attribs[$PluginName]["Version"])."', "
104  ."0, "
105  ." ".($Attribs[$PluginName]["EnabledByDefault"]
106  ? 1 : 0).")");
107  $this->DB->Query("SELECT * FROM PluginInfo"
108  ." WHERE BaseName = '".addslashes($PluginName)."'");
109  $this->PluginInfo[$PluginName] = $this->DB->FetchRow();
110  }
111 
112  # check required plugin attributes
113  if (!strlen($Attribs[$PluginName]["Name"]))
114  {
115  $this->ErrMsgs[$PluginName][] = "Plugin <b>".$PluginName."</b>"
116  ." could not be loaded because it"
117  ." did not have a <i>Name</i> attribute set.";
118  unset($this->PluginEnabled[$PluginName]);
119  unset($this->Plugins[$PluginName]);
120  }
121  if (!strlen($Attribs[$PluginName]["Version"]))
122  {
123  $this->ErrMsgs[$PluginName][] = "Plugin <b>".$PluginName."</b>"
124  ." could not be loaded because it"
125  ." did not have a <i>Version</i> attribute set.";
126  unset($this->PluginEnabled[$PluginName]);
127  unset($this->Plugins[$PluginName]);
128  }
129  }
130  else
131  {
132  $this->ErrMsgs[$PluginName][] = "Plugin <b>".$PluginName."</b>"
133  ." could not be loaded because <i>".$PluginName."</i> was"
134  ." not a subclass of base <i>Plugin</i> class";
135  }
136  }
137  else
138  {
139  $this->ErrMsgs[$PluginName][] = "Expected class <i>".$PluginName
140  ."</i> not found in plugin file <i>"
141  .$this->PluginFiles[$PluginName]."</i>";
142  }
143  }
144 
145  # check plugin dependencies
146  $this->CheckDependencies();
147 
148  # load plugin configurations
149  $this->DB->Query("SELECT BaseName,Cfg FROM PluginInfo");
150  $Cfgs = $this->DB->FetchColumn("Cfg", "BaseName");
151 
152  # sort plugins according to any loading order requests
153  $SortedPlugins = $this->SortPluginsByInitializationPrecedence($this->Plugins);
154 
155  # for each plugin
156  foreach ($SortedPlugins as $PluginName => $Plugin)
157  {
158  # if plugin is enabled
159  if ($this->PluginEnabled[$PluginName])
160  {
161  # if plugin has its own subdirectory
162  if ($this->PluginHasDir[$PluginName])
163  {
164  # if plugin has its own object directory
165  $Dir = dirname($this->PluginFiles[$PluginName]);
166  if (is_dir($Dir."/objects"))
167  {
168  # add object directory to class autoloading list
169  ApplicationFramework::AddObjectDirectory($Dir."/objects");
170  }
171  else
172  {
173  # add plugin directory to class autoloading list
174  ApplicationFramework::AddObjectDirectory($Dir);
175  }
176  }
177 
178  # set configuration values if available
179  if (isset($Cfgs[$PluginName]))
180  {
181  $Plugin->SetAllCfg(unserialize($Cfgs[$PluginName]));
182  }
183 
184  # install or upgrade plugin if needed
185  $this->InstallPlugin($Plugin);
186 
187  # recheck dependencies if enabled status has changed
188  if (!$this->PluginEnabled[$PluginName])
189  {
190  $this->CheckDependencies();
191  }
192  }
193 
194  # if plugin is enabled or we are loading all configuration options
195  if ($ForcePluginConfigOptLoad || $this->PluginEnabled[$PluginName])
196  {
197  # set up plugin configuration options
198  $ErrMsgs = $Plugin->SetUpConfigOptions();
199  if ($ErrMsgs !== NULL)
200  {
201  if (!is_array($ErrMsgs)) { $ErrMsgs = array($ErrMsgs); }
202  foreach ($ErrMsgs as $ErrMsg)
203  {
204  $this->ErrMsgs[$PluginName][] = "Configuration option setup"
205  ." failed for plugin <b>".$PluginName."</b>: <i>"
206  .$ErrMsg."</i>";
207  }
208  $this->PluginEnabled[$PluginName] = FALSE;
209  }
210  }
211 
212  # if plugin is enabled
213  if ($this->PluginEnabled[$PluginName])
214  {
215  # initialize the plugin
216  $ErrMsgs = $Plugin->Initialize();
217 
218  # if initialization failed
219  if ($ErrMsgs !== NULL)
220  {
221  if (!is_array($ErrMsgs)) { $ErrMsgs = array($ErrMsgs); }
222  foreach ($ErrMsgs as $ErrMsg)
223  {
224  $this->ErrMsgs[$PluginName][] = "Initialization failed for"
225  ." plugin <b>".$PluginName."</b>: <i>".$ErrMsg."</i>";
226  }
227  $this->PluginEnabled[$PluginName] = FALSE;
228  }
229  else
230  {
231  # register any events declared by plugin
232  $Events = $Plugin->DeclareEvents();
233  if (count($Events)) { $this->AF->RegisterEvent($Events); }
234 
235  # set up any event hooks requested by plugin
236  $EventsToHook = $Plugin->HookEvents();
237  if (count($EventsToHook))
238  {
239  foreach ($EventsToHook as $EventName => $PluginMethods)
240  {
241  if (!is_array($PluginMethods))
242  { $PluginMethods = array($PluginMethods); }
243 
244  foreach ($PluginMethods as $PluginMethod)
245  {
246  if ($this->AF->IsStaticOnlyEvent($EventName))
247  {
248  $Caller = new PluginCaller(
249  $PluginName, $PluginMethod);
250  $Result = $this->AF->HookEvent(
251  $EventName,
252  array($Caller, "CallPluginMethod"));
253  }
254  else
255  {
256  $Result = $this->AF->HookEvent(
257  $EventName, array($Plugin, $PluginMethod));
258  }
259  if ($Result === FALSE)
260  {
261  $this->ErrMsgs[$PluginName][] =
262  "Unable to hook requested event <i>"
263  .$EventName."</i> for plugin <b>"
264  .$PluginName."</b>";
265  $this->PluginEnabled[$PluginName] = FALSE;
266  }
267  }
268  }
269  }
270 
271  # mark plugin initialization as complete
272  if ($this->PluginEnabled[$PluginName])
273  {
274  $this->PluginReady[$PluginName] = TRUE;
275  }
276  }
277 
278  # recheck dependencies if enabled status has changed
279  if (!$this->PluginEnabled[$PluginName])
280  {
281  $this->CheckDependencies();
282  }
283  }
284  }
285 
286  # check plugin dependencies again in case an install or upgrade failed
287  $this->CheckDependencies();
288 
289  # report to caller whether any problems were encountered
290  return count($this->ErrMsgs) ? FALSE : TRUE;
291  }
292 
298  function GetErrorMessages()
299  {
300  return $this->ErrMsgs;
301  }
302 
309  function GetStatusMessages()
310  {
311  return $this->StatusMsgs;
312  }
313 
319  function GetPlugin($PluginName)
320  {
321  # pages where we need to access plugins even if they aren't
322  # yet ready
323  $AllowUnreadyPages = array("PluginConfig", "PluginUninstall");
324 
325  if (!array_key_exists($PluginName, $this->PluginReady) &&
326  !in_array($GLOBALS["AF"]->GetPageName(), $AllowUnreadyPages) &&
327  !(basename($_SERVER["SCRIPT_NAME"]) == "installcwis.php") )
328  {
329  throw new Exception("Attempt to access plugin ".$PluginName
330  ." that has not been initialized.");
331  }
332  return isset($this->Plugins[$PluginName])
333  ? $this->Plugins[$PluginName] : NULL;
334  }
335 
344  {
345  return $this->GetPlugin($this->PageFilePlugin);
346  }
347 
353  {
354  $Info = array();
355  foreach ($this->Plugins as $PluginName => $Plugin)
356  {
357  $Info[$PluginName] = $Plugin->GetAttributes();
358  $Info[$PluginName]["Enabled"] =
359  isset($this->PluginInfo[$PluginName]["Enabled"])
360  ? $this->PluginInfo[$PluginName]["Enabled"] : FALSE;
361  $Info[$PluginName]["Installed"] =
362  isset($this->PluginInfo[$PluginName]["Installed"])
363  ? $this->PluginInfo[$PluginName]["Installed"] : FALSE;
364  $Info[$PluginName]["ClassFile"] = $this->PluginFiles[$PluginName];
365  }
366  return $Info;
367  }
368 
374  function GetDependents($PluginName)
375  {
376  $Dependents = array();
377  $AllAttribs = $this->GetPluginAttributes();
378  foreach ($AllAttribs as $Name => $Attribs)
379  {
380  if (array_key_exists($PluginName, $Attribs["Requires"]))
381  {
382  $Dependents[] = $Name;
383  $SubDependents = $this->GetDependents($Name);
384  $Dependents = array_merge($Dependents, $SubDependents);
385  }
386  }
387  return $Dependents;
388  }
389 
395  {
396  return array_keys($this->PluginEnabled, 1);
397  }
398 
405  function PluginEnabled($PluginName, $NewValue = NULL)
406  {
407  # if an enabled/disabled value was supplied
408  if ($NewValue !== NULL)
409  {
410  # if enabled/disabled status has changed for plugin
411  if (($NewValue && !$this->PluginInfo[$PluginName]["Enabled"])
412  || (!$NewValue && $this->PluginInfo[$PluginName]["Enabled"]))
413  {
414  $Info = $this->Plugins[$PluginName]->GetAttributes();
415  $this->DB->Query("UPDATE PluginInfo"
416  ." SET Enabled = ".($NewValue ? "1" : "0")
417  ." WHERE BaseName = '".addslashes($PluginName)."'");
418  $this->PluginEnabled[$PluginName] = $NewValue;
419  $this->PluginInfo[$PluginName]["Enabled"] = $NewValue;
420  $this->StatusMsgs[$PluginName][] = "Plugin <i>"
421  .$Info["Name"]."</i> "
422  .($NewValue ? "enabled" : "disabled").".";
423  }
424  }
425  return array_key_exists($PluginName, $this->PluginEnabled)
426  && $this->PluginEnabled[$PluginName];
427  }
428 
434  function UninstallPlugin($PluginName)
435  {
436  # assume success
437  $Result = NULL;
438 
439  # if plugin is installed
440  if ($this->PluginInfo[$PluginName]["Installed"])
441  {
442  # call uninstall method for plugin
443  $Result = $this->Plugins[$PluginName]->Uninstall();
444 
445  # if plugin uninstall method succeeded
446  if ($Result === NULL)
447  {
448  # remove plugin info from database
449  $this->DB->Query("DELETE FROM PluginInfo"
450  ." WHERE BaseName = '".addslashes($PluginName)."'");
451 
452  # drop our data for the plugin
453  unset($this->Plugins[$PluginName]);
454  unset($this->PluginInfo[$PluginName]);
455  unset($this->PluginEnabled[$PluginName]);
456  unset($this->PluginNames[$PluginName]);
457  unset($this->PluginFiles[$PluginName]);
458  }
459  }
460 
461  # report results (if any) to caller
462  return $Result;
463  }
464 
465 
466  # ---- PRIVATE INTERFACE -------------------------------------------------
467 
468  private $AF;
469  private $DB;
470  private $DirsToSearch;
471  private $ErrMsgs = array();
472  private $PageFilePlugin = NULL;
473  private $Plugins = array();
474  private $PluginEnabled = array();
475  private $PluginFiles = array();
476  private $PluginHasDir = array();
477  private $PluginInfo = array();
478  private $PluginNames = array();
479  private $PluginReady = array();
480  private $StatusMsgs = array();
481 
487  private function FindPlugins($DirsToSearch)
488  {
489  # for each directory
490  foreach ($DirsToSearch as $Dir)
491  {
492  # if directory exists
493  if (is_dir($Dir))
494  {
495  # for each file in directory
496  $FileNames = scandir($Dir);
497  foreach ($FileNames as $FileName)
498  {
499  # if file looks like base plugin file
500  if (preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*\.php$/", $FileName))
501  {
502  # add file to list
503  $PluginName = preg_replace("/\.php$/", "", $FileName);
504  $this->PluginNames[$PluginName] = $PluginName;
505  $this->PluginFiles[$PluginName] = $Dir."/".$FileName;
506  $this->PluginHasDir[$PluginName] = FALSE;
507  }
508  # else if file looks like plugin directory
509  elseif (is_dir($Dir."/".$FileName)
510  && preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*/", $FileName))
511  {
512  # if there is a base plugin file in the directory
513  $PluginName = $FileName;
514  $PluginFile = $Dir."/".$PluginName."/".$PluginName.".php";
515  if (file_exists($PluginFile))
516  {
517  # add plugin and directory to lists
518  $this->PluginNames[$PluginName] = $PluginName;
519  $this->PluginFiles[$PluginName] = $PluginFile;
520  $this->PluginHasDir[$PluginName] = TRUE;
521  }
522  # else if we don't already have a base plugin file
523  elseif (!array_key_exists($PluginName, $this->PluginFiles))
524  {
525  $this->ErrMsgs[$PluginName][] =
526  "Expected plugin file <i>".$PluginName.".php</i> not"
527  ." found in plugin subdirectory <i>"
528  .$Dir."/".$PluginName."</i>";
529  }
530  }
531  }
532  }
533  }
534  }
535 
541  private function InstallPlugin($Plugin)
542  {
543  # if plugin is enabled
544  $PluginName = get_class($Plugin);
545  if ($this->PluginEnabled[$PluginName])
546  {
547  # if plugin has not been installed
548  $Attribs = $Plugin->GetAttributes();
549  if (!$this->PluginInfo[$PluginName]["Installed"])
550  {
551  # set default values if present
552  if (isset($Attribs["CfgSetup"]))
553  {
554  foreach ($Attribs["CfgSetup"] as $CfgValName => $CfgSetup)
555  {
556  if (isset($CfgSetup["Default"]))
557  {
558  $Plugin->ConfigSetting($CfgValName,
559  $CfgSetup["Default"]);
560  }
561  }
562  }
563 
564  # install plugin
565  $ErrMsg = $Plugin->Install();
566 
567  # if install succeeded
568  if ($ErrMsg == NULL)
569  {
570  # mark plugin as installed
571  $this->DB->Query("UPDATE PluginInfo SET Installed = 1"
572  ." WHERE BaseName = '".addslashes($PluginName)."'");
573  $this->PluginInfo[$PluginName]["Installed"] = 1;
574  }
575  else
576  {
577  # disable plugin
578  $this->PluginEnabled[$PluginName] = FALSE;
579 
580  # record error message about installation failure
581  $this->ErrMsgs[$PluginName][] = "Installation of plugin <b>"
582  .$PluginName."</b> failed: <i>".$ErrMsg."</i>";;
583  }
584  }
585  else
586  {
587  # if plugin version is newer than version in database
588  if (version_compare($Attribs["Version"],
589  $this->PluginInfo[$PluginName]["Version"]) == 1)
590  {
591  # set default values for any new configuration settings
592  if (isset($Attribs["CfgSetup"]))
593  {
594  foreach ($Attribs["CfgSetup"] as $CfgValName => $CfgSetup)
595  {
596  if (isset($CfgSetup["Default"])
597  && ($Plugin->ConfigSetting(
598  $CfgValName) === NULL))
599  {
600  $Plugin->ConfigSetting($CfgValName,
601  $CfgSetup["Default"]);
602  }
603  }
604  }
605 
606  # upgrade plugin
607  $ErrMsg = $Plugin->Upgrade(
608  $this->PluginInfo[$PluginName]["Version"]);
609 
610  # if upgrade succeeded
611  if ($ErrMsg == NULL)
612  {
613  # update plugin version in database
614  $this->DB->Query("UPDATE PluginInfo"
615  ." SET Version = '".addslashes($Attribs["Version"])."'"
616  ." WHERE BaseName = '".addslashes($PluginName)."'");
617  $this->PluginInfo[$PluginName]["Version"] = $Attribs["Version"];
618  }
619  else
620  {
621  # disable plugin
622  $this->PluginEnabled[$PluginName] = FALSE;
623 
624  # record error message about upgrade failure
625  $this->ErrMsgs[$PluginName][] = "Upgrade of plugin <b>"
626  .$PluginName."</b> from version <i>"
627  .addslashes($this->PluginInfo[$PluginName]["Version"])
628  ."</i> to version <i>"
629  .addslashes($Attribs["Version"])."</i> failed: <i>"
630  .$ErrMsg."</i>";
631  }
632  }
633  # else if plugin version is older than version in database
634  elseif (version_compare($Attribs["Version"],
635  $this->PluginInfo[$PluginName]["Version"]) == -1)
636  {
637  # disable plugin
638  $this->PluginEnabled[$PluginName] = FALSE;
639 
640  # record error message about version conflict
641  $this->ErrMsgs[$PluginName][] = "Plugin <b>"
642  .$PluginName."</b> is older (<i>"
643  .addslashes($Attribs["Version"])
644  ."</i>) than previously-installed version (<i>"
645  .addslashes(
646  $this->PluginInfo[$PluginName]["Version"]
647  )."</i>).";
648  }
649  }
650  }
651  }
652 
657  private function CheckDependencies()
658  {
659  # look until all enabled plugins check out okay
660  do
661  {
662  # start out assuming all plugins are okay
663  $AllOkay = TRUE;
664 
665  # for each plugin
666  foreach ($this->Plugins as $PluginName => $Plugin)
667  {
668  # if plugin is currently enabled
669  if ($this->PluginEnabled[$PluginName])
670  {
671  # load plugin attributes
672  if (!isset($Attribs[$PluginName]))
673  {
674  $Attribs[$PluginName] =
675  $this->Plugins[$PluginName]->GetAttributes();
676  }
677 
678  # for each dependency for this plugin
679  foreach ($Attribs[$PluginName]["Requires"]
680  as $ReqName => $ReqVersion)
681  {
682  # handle PHP version requirements
683  if ($ReqName == "PHP")
684  {
685  if (version_compare($ReqVersion, phpversion(), ">"))
686  {
687  $this->ErrMsgs[$PluginName][] = "PHP version "
688  ."<i>".$ReqVersion."</i>"
689  ." required by <b>".$PluginName."</b>"
690  ." was not available. (Current PHP version"
691  ." is <i>".phpversion()."</i>.)";
692  }
693  }
694  # handle PHP extension requirements
695  elseif (preg_match("/^PHPX_/", $ReqName))
696  {
697  list($Dummy, $ExtensionName) = explode("_", $ReqName, 2);
698  if (!extension_loaded($ExtensionName))
699  {
700  $this->ErrMsgs[$PluginName][] = "PHP extension "
701  ."<i>".$ExtensionName."</i>"
702  ." required by <b>".$PluginName."</b>"
703  ." was not available.";
704  }
705  }
706  # handle dependencies on other plugins
707  else
708  {
709  # load plugin attributes if not already loaded
710  if (isset($this->Plugins[$ReqName])
711  && !isset($Attribs[$ReqName]))
712  {
713  $Attribs[$ReqName] =
714  $this->Plugins[$ReqName]->GetAttributes();
715  }
716 
717  # if target plugin is not present or is disabled or is too old
718  if (!isset($this->PluginEnabled[$ReqName])
719  || !$this->PluginEnabled[$ReqName]
720  || (version_compare($ReqVersion,
721  $Attribs[$ReqName]["Version"], ">")))
722  {
723  # add error message and disable plugin
724  $this->ErrMsgs[$PluginName][] = "Plugin <i>"
725  .$ReqName." ".$ReqVersion."</i>"
726  ." required by <b>".$PluginName."</b>"
727  ." was not available.";
728  $this->PluginEnabled[$PluginName] = FALSE;
729  $this->PluginReady[$PluginName] = FALSE;
730 
731  # set flag indicating plugin did not check out
732  $AllOkay = FALSE;
733  }
734  }
735  }
736  }
737  }
738  } while ($AllOkay == FALSE);
739  }
740 
749  private function SortPluginsByInitializationPrecedence($Plugins)
750  {
751  # determine initialization order
752  $PluginAttribs = $this->GetPluginAttributes();
753  $PluginsAfterUs = array();
754  foreach ($PluginAttribs as $PluginName => $Attribs)
755  {
756  foreach ($Attribs["InitializeBefore"] as $OtherPluginName)
757  {
758  $PluginsAfterUs[$PluginName][] = $OtherPluginName;
759  }
760  foreach ($Attribs["InitializeAfter"] as $OtherPluginName)
761  {
762  $PluginsAfterUs[$OtherPluginName][] = $PluginName;
763  }
764  }
765 
766  # infer other initialization order cues from lists of required plugins
767  foreach ($PluginAttribs as $PluginName => $Attribs)
768  {
769  # for each required plugin
770  foreach ($Attribs["Requires"]
771  as $RequiredPluginName => $RequiredPluginVersion)
772  {
773  # if there is not a requirement in the opposite direction
774  if (!array_key_exists($PluginName,
775  $PluginAttribs[$RequiredPluginName]["Requires"]))
776  {
777  # if the required plugin is not scheduled to be after us
778  if (!array_key_exists($PluginName, $PluginsAfterUs)
779  || !in_array($RequiredPluginName,
780  $PluginsAfterUs[$PluginName]))
781  {
782  # if we are not already scheduled to be after the required plugin
783  if (!array_key_exists($PluginName, $PluginsAfterUs)
784  || !in_array($RequiredPluginName,
785  $PluginsAfterUs[$PluginName]))
786  {
787  # schedule us to be after the required plugin
788  $PluginsAfterUs[$RequiredPluginName][] =
789  $PluginName;
790  }
791  }
792  }
793  }
794  }
795 
796  # keep track of those plugins we have yet to do and those that are done
797  $UnsortedPlugins = array_keys($Plugins);
798  $PluginsProcessed = array();
799 
800  # limit the number of iterations of the plugin ordering loop
801  # to 10 times the number of plugins we have
802  $MaxIterations = 10 * count($UnsortedPlugins);
803  $IterationCount = 0;
804 
805  # iterate through all the plugins that need processing
806  while (($NextPlugin = array_shift($UnsortedPlugins)) !== NULL)
807  {
808  # check to be sure that we're not looping forever
809  $IterationCount++;
810  if ($IterationCount > $MaxIterations)
811  {
812  throw new Exception(
813  "Max iteration count exceeded trying to determine plugin"
814  ." loading order. Is there a dependency loop?");
815  }
816 
817  # if no plugins require this one, it can go last
818  if (!isset($PluginsAfterUs[$NextPlugin]))
819  {
820  $PluginsProcessed[$NextPlugin] = $MaxIterations;
821  }
822  else
823  {
824  # for plugins that are required by others
825  $Index = $MaxIterations;
826  foreach ($PluginsAfterUs[$NextPlugin] as $GoBefore)
827  {
828  if (!isset($PluginsProcessed[$GoBefore]))
829  {
830  # if there is something that requires us which hasn't
831  # yet been assigned an order, then we can't determine
832  # our own place on this iteration
833  array_push($UnsortedPlugins, $NextPlugin);
834  continue 2;
835  }
836  else
837  {
838  # otherwise, make sure that we're loaded
839  # before the earliest of the things that require us
840  $Index = min($Index, $PluginsProcessed[$GoBefore] - 1);
841  }
842  }
843  $PluginsProcessed[$NextPlugin] = $Index;
844  }
845  }
846 
847  # arrange plugins according to our ordering
848  asort($PluginsProcessed, SORT_NUMERIC);
849  $SortedPlugins = array();
850  foreach ($PluginsProcessed as $PluginName => $SortOrder)
851  {
852  $SortedPlugins[$PluginName] = $Plugins[$PluginName];
853  }
854 
855  # return sorted list to caller
856  return $SortedPlugins;
857  }
858 
867  function FindPluginPhpFile($PageName)
868  {
869  # build list of possible locations for file
870  $Locations = array(
871  "local/plugins/%PLUGIN%/pages/%PAGE%.php",
872  "plugins/%PLUGIN%/pages/%PAGE%.php",
873  "local/plugins/%PLUGIN%/%PAGE%.php",
874  "plugins/%PLUGIN%/%PAGE%.php",
875  );
876 
877  # look for file and return (possibly) updated page to caller
878  return $this->FindPluginPageFile($PageName, $Locations);
879  }
890  function FindPluginHtmlFile($PageName)
891  {
892  # build list of possible locations for file
893  $Locations = array(
894  "local/plugins/%PLUGIN%/interface/%ACTIVEUI%/%PAGE%.html",
895  "plugins/%PLUGIN%/interface/%ACTIVEUI%/%PAGE%.html",
896  "local/plugins/%PLUGIN%/interface/default/%PAGE%.html",
897  "plugins/%PLUGIN%/interface/default/%PAGE%.html",
898  "local/plugins/%PLUGIN%/%PAGE%.html",
899  "plugins/%PLUGIN%/%PAGE%.html",
900  );
901 
902  # find HTML file
903  $Params = $this->FindPluginPageFile($PageName, $Locations);
904 
905  # if plugin HTML file was found
906  if ($Params["PageName"] != $PageName)
907  {
908  # add subdirectories for plugin to search paths
909  $Dir = preg_replace("%^local/%", "", dirname($Params["PageName"]));
910  $GLOBALS["AF"]->AddImageDirectories(array(
911  "local/".$Dir."/images",
912  $Dir."/images",
913  ));
914  $GLOBALS["AF"]->AddIncludeDirectories(array(
915  "local/".$Dir."/include",
916  $Dir."/include",
917  ));
918  $GLOBALS["AF"]->AddFunctionDirectories(array(
919  "local/".$Dir."/include",
920  $Dir."/include",
921  ));
922  }
923 
924  # return possibly revised HTML file name to caller
925  return $Params;
926  }
937  private function FindPluginPageFile($PageName, $Locations)
938  {
939  # set up return value assuming we will not find plugin page file
940  $ReturnValue["PageName"] = $PageName;
941 
942  # look for plugin name and plugin page name in base page name
943  preg_match("/P_([A-Za-z].[A-Za-z0-9]*)_([A-Za-z0-9_-]+)/", $PageName, $Matches);
944 
945  # if base page name contained name of enabled plugin with its own subdirectory
946  if ((count($Matches) == 3)
947  && $this->PluginEnabled[$Matches[1]]
948  && $this->PluginHasDir[$Matches[1]])
949  {
950  # for each possible location
951  $ActiveUI = $GLOBALS["AF"]->ActiveUserInterface();
952  $PluginName = $Matches[1];
953  $PageName = $Matches[2];
954  foreach ($Locations as $Loc)
955  {
956  # make any needed substitutions into path
957  $FileName = str_replace(array("%ACTIVEUI%", "%PLUGIN%", "%PAGE%"),
958  array($ActiveUI, $PluginName, $PageName), $Loc);
959 
960  # if file exists in this location
961  if (file_exists($FileName))
962  {
963  # set return value to contain full plugin page file name
964  $ReturnValue["PageName"] = $FileName;
965 
966  # save plugin name as home of current page
967  $this->PageFilePlugin = $PluginName;
968 
969  # set G_Plugin to plugin associated with current page
970  $GLOBALS["G_Plugin"] = $this->GetPluginForCurrentPage();
971 
972  # stop looking
973  break;
974  }
975  }
976  }
977 
978  # return array containing page name or page file name to caller
979  return $ReturnValue;
980  }
981 
989  static function CfgSaveCallback($BaseName, $Cfg)
990  {
991  $DB = new Database();
992  $DB->Query("UPDATE PluginInfo SET Cfg = '".addslashes(serialize($Cfg))
993  ."' WHERE BaseName = '".addslashes($BaseName)."'");
994  }
996 }
997 
1009 class PluginCaller {
1010 
1017  function __construct($PluginName, $MethodName)
1018  {
1019  $this->PluginName = $PluginName;
1020  $this->MethodName = $MethodName;
1021  }
1022 
1028  function CallPluginMethod()
1029  {
1030  $Args = func_get_args();
1031  $Plugin = self::$Manager->GetPlugin($this->PluginName);
1032  return call_user_func_array(array($Plugin, $this->MethodName), $Args);
1033  }
1034 
1039  function GetCallbackAsText()
1040  {
1041  return $this->PluginName."::".$this->MethodName;
1042  }
1043 
1049  function __sleep()
1050  {
1051  return array("PluginName", "MethodName");
1052  }
1053 
1055  static public $Manager;
1056 
1057  private $PluginName;
1058  private $MethodName;
1059 }
1062 ?>
LoadPlugins($ForcePluginConfigOptLoad=FALSE)
Load and initialize plugins.
GetErrorMessages()
Retrieve any error messages generated during plugin loading.
Manager to load and invoke plugins.
SQL database abstraction object with smart query caching.
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.
GetPlugin($PluginName)
Retrieve specified plugin.
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).
GetStatusMessages()
Get/set any status messages generated during plugin loading or via plugin method calls.