1<?php
2/***********************************************************
3 * Copyright (C) 2008-2012 Hewlett-Packard Development Company, L.P.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License version 2.1 as published by the Free Software Foundation.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library; if not, write to the Free Software Foundation, Inc.0
16 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17 ***********************************************************/
18use Fossology\Lib\Plugin\Plugin;
19
20/**
21 * \file
22 * \brief Core functions for user interface plugins
23 **/
24
25/**
26 * \brief Global plugins array
27 **/
28global $Plugins;
29$Plugins = array();
30
31/**
32 * @brief Sort compare function.
33 *
34 * Sorts by dependency relationship.  If a and b are at the same
35 * dependency level, then sort by the plugin level.
36 *
37 * @param Plugin a
38 * @param Plugin b
39 *
40 * @return -1, 0, 1 for plugin a being <, =, or > than b
41 */
42function plugin_cmp($a, $b)
43{
44  /* Sort by plugin version only when the name is the same */
45  if (0 == strcmp($a->Name, $b->Name)) {
46    /* Sort by plugin version (descending order) */
47    $rc = strcmp($a->Version, $b->Version);
48    if ($rc != 0) {
49      return (- $rc);
50    }
51  }
52
53  /* Sort by dependencies. */
54  /* check if $a is a dependency for $b */
55  // print "BEGIN Comparing $a->Name with $b->Name\n";
56  foreach ($a->Dependency as $val) {
57    // print "Comparing $a->Name :: $val with $b->Name\n";
58    if ($val == $b->Name) {
59      return (1);
60    }
61  }
62  /* check if $b is a dependency for $a */
63  foreach ($b->Dependency as $val) {
64    // print "Comparing $b->Name :: $val with $a->Name\n";
65    if ($val == $a->Name) {
66      return (- 1);
67    }
68  }
69  // print "STILL Comparing $a->Name with $b->Name\n";
70
71  /* If same dependencies, then sort by plugin level (highest comes first) */
72  if ($a->PluginLevel > $b->PluginLevel) {
73    return (- 1);
74  } elseif ($a->PluginLevel < $b->PluginLevel) {
75    return (1);
76  }
77
78  /* Nothing else to sort by -- sort by number of dependencies */
79  $rc = count($a->Dependency) - count($b->Dependency);
80  return ($rc);
81} // plugin_cmp()
82
83/**
84 * \brief Disable all plugins that have a level greater than the users permission level.
85 *
86 * \param int $Level The user's DBaccess level
87 * \return void
88 */
89function plugin_disable($Level)
90{
91  /** @var Plugin[] $Plugins */
92  global $Plugins;
93
94  /* Disable all plugins with >= $Level access */
95  //echo "<pre>COMP: starting to disable plugins\n</pre>";
96  $LoginFlag = empty($_SESSION['User']);
97  foreach ($Plugins as $pluginName => &$P) {
98    if ($P->State == PLUGIN_STATE_INVALID) {
99      // echo "<pre>COMP: Plugin $P->Name is in INVALID state\n</pre>";
100      continue;
101    }
102    if ($P->DBaccess > $Level) {
103      // echo "<pre>COMP: Going to disable $P->Name\n</pre>";
104      // echo "<pre>COMP: disabling plugins with $P->DBaccess >=
105      // $Level\n</pre>";
106      $P->unInstall();
107      unset($Plugins[$pluginName]);
108    }
109    unset($P);
110  }
111} // plugin_disable
112
113/**
114 * \brief Sort the global $Plugins by dependencies.  This way plugins
115 *        get loaded in the correct order.
116 **/
117function plugin_sort()
118{
119  global $Plugins;
120
121  /* Ideally, I would like to use usort.  However, there are
122   dependency issues.  Specifically: usort only works where there are
123   direct comparisons.  It does not work with indirect dependencies.
124   For example:
125   A depends on B (A->B).
126   B depends on C (B->C).
127   If I just use usort, then C may be sorted AFTER A since there is
128   no explicit link from A->C.  The array (B,A,C) is a possible usort
129   return, and it is wrong.
130   Before I can sort, we must fill out the dependency arrays.
131   */
132
133  /* for each plugin, store the dependencies in a matrix */
134  $DepArray = array();
135  foreach ($Plugins as &$P) {
136    if (empty($P->Dependency[0])) {
137      continue; // ignore no dependencies
138    }
139    $DepArray[$P->Name] = array();
140    $D = &$DepArray[$P->Name];
141    for ($j = 0; $j < count($P->Dependency); $j ++) {
142      $D[$P->Dependency[$j]] = $P->PluginLevel;
143    }
144    unset($P);
145  }
146
147  /* Now iterate through the array.
148   This converts implied dependencies into direct dependencies. */
149  foreach ($DepArray as $A => $a) {
150    $Aa = &$DepArray[$A];
151    /*
152     * Find every element that depends on this element and merge the
153     * dependency lists
154     */
155    foreach ($DepArray as $B => $b) {
156      $Bb = $DepArray[$B];
157      if (! empty($Bb[$A])) {
158        /* merge in the entire list */
159        $DepArray[$B] = array_merge($Aa, $Bb);
160      }
161    }
162  }
163
164  /* Finally: Put the direct dependencies back into the structures */
165  foreach ($Plugins as &$P) {
166    if (empty($P->Dependency[0])) {
167      continue; // ignore no dependencies
168    }
169    $P->Dependency = array_keys($DepArray[$P->Name]);
170    unset($P);
171  }
172
173  /* Now it is safe to sort */
174  uasort($Plugins, 'plugin_cmp');
175} // plugin_sort()
176
177/**
178 * \brief Given the official name of a plugin, find the index to it in the
179 *        global $Plugins array.
180 *
181 * \note Only plugins in PLUGIN_STATE_READY are scanned.
182 * \param $Name Plugin name
183 * \return -1 if the plugin $Name is not found.
184 **/
185function plugin_find_id($pluginName)
186{
187  /** \todo has to be removed */
188  /** @var Plugin[] $Plugins */
189  global $Plugins;
190
191  if (array_key_exists($pluginName, $Plugins)) {
192    $plugin = $Plugins[$pluginName];
193    return $plugin->State === PLUGIN_STATE_READY ? $pluginName : - 1;
194  }
195
196  return -1;
197}
198
199/**
200 * @brief Given the official name of a plugin, return the $Plugins object.
201 *
202 * Only plugins in PLUGIN_STATE_READY are scanned.
203 *
204 * @param string $pluginName Name of the required plugin
205 * @return Plugin|NULL The plugin or NULL if the plugin name isn't found.
206 */
207function plugin_find($pluginName)
208{
209  global $Plugins;
210  return array_key_exists($pluginName, $Plugins) ? $Plugins[$pluginName] : null;
211}
212
213/**
214 * \brief Initialize every plugin in the global $Plugins array.
215 *
216 * plugin_sort() is called followed by the plugin
217 * PostInitialize() if PLUGIN_STATE_VALID,
218 * and RegisterMenus() if PLUGIN_STATE_READY.
219 **/
220function plugin_preinstall()
221{
222  /** @var Plugin[] $Plugins */
223  global $Plugins;
224
225  plugin_sort();
226
227  foreach (array_keys($Plugins) as $pluginName) {
228    if (array_key_exists($pluginName, $Plugins)) {
229      $Plugins[$pluginName]->preInstall();
230    }
231  }
232}
233
234/**
235 * \brief Call the Install method for every plugin
236 */
237function plugin_postinstall()
238{
239  /** @var Plugin[] $Plugins */
240  global $Plugins;
241
242  foreach ($Plugins as &$plugin) {
243    $plugin->postInstall();
244  }
245}
246
247/**
248 * \brief Load every module ui found in mods-enabled
249 **/
250function plugin_load()
251{
252  global $SYSCONFDIR;
253
254  $ModsEnabledDir = "$SYSCONFDIR/mods-enabled";
255
256  /* Open $ModsEnabledDir and include all the php files found in the ui/ subdirectory */
257
258  if (is_dir($ModsEnabledDir)) {
259    foreach (glob("$ModsEnabledDir/*") as $ModDirPath) {
260      foreach (array(
261        "/ui",
262        ""
263      ) as $subdir) {
264        $targetPath = $ModDirPath . $subdir;
265
266        if (is_dir($targetPath)) {
267          foreach (glob("$targetPath/*.php") as $phpFile) {
268            if (! strstr($phpFile, 'ndex.php')) {
269              include_once ("$phpFile");
270            }
271          }
272          break;
273        }
274      }
275    }
276  }
277}
278
279/**
280 * \brief Unload every plugin by calling its Destroy().
281 **/
282function plugin_unload()
283{
284  /** @var Plugin[] $Plugins */
285  global $Plugins;
286
287  foreach ($Plugins as $key => $plugin) {
288    if ($key == - 1) {
289      break;
290    }
291    if (empty($plugin)) {
292      continue;
293    }
294
295    $plugin->unInstall();
296  }
297} // plugin_unload()
298
299/**
300 * Register a new plugin to the global Plugins array.
301 * @param Plugin $plugin Plugin to be added
302 * @throws \Exception If plugin has no name or is already registered
303 */
304function register_plugin(Plugin $plugin)
305{
306  /** @var Plugin[] $Plugins */
307  global $Plugins;
308
309  $name = $plugin->getName();
310
311  if (empty($name)) {
312    throw new \Exception("cannot create module without name");
313  }
314
315  if (array_key_exists($name, $Plugins)) {
316    throw new \Exception("duplicate definition of plugin with name $name");
317  }
318
319  $Plugins[$name] = $plugin;
320}
321
322/**
323 * Used to convert plugin to string representation by __toString()
324 * @param array  $vars      Associative array of variable name => value
325 * @param string $classname Name of the class of the object being represented
326 * @return string String representation of the object
327 */
328function getStringRepresentation($vars, $classname)
329{
330  $output = $classname . " {\n";
331  foreach ($vars as $name => $value) {
332    if (! is_object($value)) {
333      $representation = print_r($value, true);
334      $lines = explode("\n", $representation);
335      $lines = array_map(function ($line){
336        return "      " . $line;
337      }, $lines);
338      $representation = trim(implode("\n", $lines));
339
340      $output .= "   $name: " . $representation . "\n";
341    }
342  }
343  $output .= "}\n";
344  return $output;
345}
346