Elgg  Version 2.2
 All Classes Namespaces Files Functions Variables Pages
ElggPluginPackage.php
Go to the documentation of this file.
1 <?php
20 
26  private $requiredFiles = array(
27  'start.php', 'manifest.xml'
28  );
29 
34  private $textFiles = array(
35  'README.txt', 'CHANGES.txt',
36  'INSTALL.txt', 'COPYRIGHT.txt', 'LICENSE.txt',
37 
38  'README', 'README.md', 'README.markdown'
39  );
40 
46  private $providesSupportedTypes = array(
47  'plugin', 'php_extension'
48  );
49 
55  private $depsSupportedTypes = array(
56  'elgg_release', 'php_version', 'php_extension', 'php_ini', 'plugin', 'priority',
57  );
58 
62  private $errorMsg = '';
63 
69  protected $manifest;
70 
76  protected $path;
77 
83  protected $valid = null;
84 
90  protected $id;
91 
100  public function __construct($plugin, $validate = true) {
101  $plugin_path = _elgg_services()->config->getPluginsPath();
102  // @todo wanted to avoid another is_dir() call here.
103  // should do some profiling to see how much it affects
104  if (strpos($plugin, $plugin_path) === 0 || is_dir($plugin)) {
105  // this is a path
107 
108  // the id is the last element of the array
109  $path_array = explode('/', trim($path, '/'));
110  $id = array_pop($path_array);
111  } else {
112  // this is a plugin id
113  // strict plugin names
114  if (preg_match('/[^a-z0-9\.\-_]/i', $plugin)) {
115  throw new \PluginException(_elgg_services()->translator->translate('PluginException:InvalidID', array($plugin)));
116  }
117 
118  $path = "{$plugin_path}$plugin/";
119  $id = $plugin;
120  }
121 
122  if (!is_dir($path)) {
123  throw new \PluginException(_elgg_services()->translator->translate('PluginException:InvalidPath', array($path)));
124  }
125 
126  $this->path = $path;
127  $this->id = $id;
128 
129  if ($validate && !$this->isValid()) {
130  if ($this->errorMsg) {
131  throw new \PluginException(_elgg_services()->translator->translate('PluginException:InvalidPlugin:Details',
132  array($plugin, $this->errorMsg)));
133  } else {
134  throw new \PluginException(_elgg_services()->translator->translate('PluginException:InvalidPlugin', array($plugin)));
135  }
136  }
137  }
138 
139  /********************************
140  * Validation and sanity checks *
141  ********************************/
142 
156  public function isValid() {
157  if (!isset($this->valid)) {
158  $this->valid = $this->validate();
159  }
160  return $this->valid;
161  }
162 
166  private function validate() {
167  // check required files.
168  $have_req_files = true;
169  foreach ($this->requiredFiles as $file) {
170  if (!is_readable($this->path . $file)) {
171  $have_req_files = false;
172  $this->errorMsg =
173  _elgg_services()->translator->translate('ElggPluginPackage:InvalidPlugin:MissingFile', array($file));
174  return false;
175  }
176  }
177 
178  // check required files
179  if (!$have_req_files) {
180  return $this->valid = false;
181  }
182 
183  // check for valid manifest.
184  if (!$this->loadManifest()) {
185  return false;
186  }
187 
188  if (!$this->isNamedCorrectly()) {
189  return false;
190  }
191 
192  // can't require or conflict with yourself or something you provide.
193  // make sure provides are all valid.
194  if (!$this->hasSaneDependencies()) {
195  return false;
196  }
197 
198  return true;
199  }
200 
207  private function isNamedCorrectly() {
208  $manifest = $this->getManifest();
209  if ($manifest) {
210  $required_id = $manifest->getID();
211  if (!empty($required_id) && ($required_id !== $this->id)) {
212  $this->errorMsg =
213  _elgg_services()->translator->translate('ElggPluginPackage:InvalidPlugin:InvalidId', array($required_id));
214  return false;
215  }
216  }
217  return true;
218  }
219 
231  private function hasSaneDependencies() {
232  // protection against plugins with no manifest file
233  if (!$this->getManifest()) {
234  return false;
235  }
236 
237  // Note: $conflicts and $requires are not unused. They're called dynamically
238  $conflicts = $this->getManifest()->getConflicts();
239  $requires = $this->getManifest()->getRequires();
240  $provides = $this->getManifest()->getProvides();
241 
242  foreach ($provides as $provide) {
243  // only valid provide types
244  if (!in_array($provide['type'], $this->providesSupportedTypes)) {
245  $this->errorMsg =
246  _elgg_services()->translator->translate('ElggPluginPackage:InvalidPlugin:InvalidProvides', array($provide['type']));
247  return false;
248  }
249 
250  // doesn't conflict or require any of its provides
251  $name = $provide['name'];
252  foreach (array('conflicts', 'requires') as $dep_type) {
253  foreach (${$dep_type} as $dep) {
254  if (!in_array($dep['type'], $this->depsSupportedTypes)) {
255  $this->errorMsg =
256  _elgg_services()->translator->translate('ElggPluginPackage:InvalidPlugin:InvalidDependency', array($dep['type']));
257  return false;
258  }
259 
260  // make sure nothing is providing something it conflicts or requires.
261  if (isset($dep['name']) && $dep['name'] == $name) {
262  $version_compare = version_compare($provide['version'], $dep['version'], $dep['comparison']);
263 
264  if ($version_compare) {
265  $this->errorMsg =
266  _elgg_services()->translator->translate('ElggPluginPackage:InvalidPlugin:CircularDep',
267  array($dep['type'], $dep['name'], $this->id));
268 
269  return false;
270  }
271  }
272  }
273  }
274  }
275 
276  return true;
277  }
278 
279 
280  /************
281  * Manifest *
282  ************/
283 
289  public function getManifest() {
290  if (!$this->manifest) {
291  if (!$this->loadManifest()) {
292  return false;
293  }
294  }
295 
296  return $this->manifest;
297  }
298 
305  private function loadManifest() {
306  $file = $this->path . 'manifest.xml';
307 
308  try {
309  $this->manifest = new \ElggPluginManifest($file, $this->id);
310  } catch (Exception $e) {
311  $this->errorMsg = $e->getMessage();
312  return false;
313  }
314 
315  if ($this->manifest instanceof \ElggPluginManifest) {
316  return true;
317  }
318 
319  $this->errorMsg = _elgg_services()->translator->translate('unknown_error');
320  return false;
321  }
322 
323  /****************
324  * Readme Files *
325  ***************/
326 
332  public function getTextFilenames() {
333  return $this->textFiles;
334  }
335 
336  /***********************
337  * Dependencies system *
338  ***********************/
339 
355  public function checkDependencies($full_report = false) {
356  // Note: $conflicts and $requires are not unused. They're called dynamically
357  $requires = $this->getManifest()->getRequires();
358  $conflicts = $this->getManifest()->getConflicts();
359 
360  $enabled_plugins = elgg_get_plugins('active');
361  $this_id = $this->getID();
362  $report = array();
363 
364  // first, check if any active plugin conflicts with us.
365  foreach ($enabled_plugins as $plugin) {
366  $temp_conflicts = array();
367  $temp_manifest = $plugin->getManifest();
368  if ($temp_manifest instanceof \ElggPluginManifest) {
369  $temp_conflicts = $plugin->getManifest()->getConflicts();
370  }
371  foreach ($temp_conflicts as $conflict) {
372  if ($conflict['type'] == 'plugin' && $conflict['name'] == $this_id) {
373  $result = $this->checkDepPlugin($conflict, $enabled_plugins, false);
374 
375  // rewrite the conflict to show the originating plugin
376  $conflict['name'] = $plugin->getManifest()->getName();
377 
378  if (!$full_report && !$result['status']) {
379  $this->errorMsg = "Conflicts with plugin \"{$plugin->getManifest()->getName()}\".";
380  return $result['status'];
381  } else {
382  $report[] = array(
383  'type' => 'conflicted',
384  'dep' => $conflict,
385  'status' => $result['status'],
386  'value' => $this->getManifest()->getVersion()
387  );
388  }
389  }
390  }
391  }
392 
393  $check_types = array('requires', 'conflicts');
394 
395  if ($full_report) {
396  // Note: $suggests is not unused. It's called dynamically
397  $suggests = $this->getManifest()->getSuggests();
398  $check_types[] = 'suggests';
399  }
400 
401  foreach ($check_types as $dep_type) {
402  $inverse = ($dep_type == 'conflicts') ? true : false;
403 
404  foreach (${$dep_type} as $dep) {
405  switch ($dep['type']) {
406  case 'elgg_release':
407  $result = $this->checkDepElgg($dep, elgg_get_version(true), $inverse);
408  break;
409 
410  case 'plugin':
411  $result = $this->checkDepPlugin($dep, $enabled_plugins, $inverse);
412  break;
413 
414  case 'priority':
415  $result = $this->checkDepPriority($dep, $enabled_plugins, $inverse);
416  break;
417 
418  case 'php_version':
419  $result = $this->checkDepPhpVersion($dep, $inverse);
420  break;
421 
422  case 'php_extension':
423  $result = $this->checkDepPhpExtension($dep, $inverse);
424  break;
425 
426  case 'php_ini':
427  $result = $this->checkDepPhpIni($dep, $inverse);
428  break;
429 
430  default:
431  $result = null;//skip further check
432  break;
433  }
434 
435  if ($result !== null) {
436  // unless we're doing a full report, break as soon as we fail.
437  if (!$full_report && !$result['status']) {
438  $this->errorMsg = "Missing dependencies.";
439  return $result['status'];
440  } else {
441  // build report element and comment
442  $report[] = array(
443  'type' => $dep_type,
444  'dep' => $dep,
445  'status' => $result['status'],
446  'value' => $result['value']
447  );
448  }
449  }
450  }
451  }
452 
453  if ($full_report) {
454  // add provides to full report
455  $provides = $this->getManifest()->getProvides();
456 
457  foreach ($provides as $provide) {
458  $report[] = array(
459  'type' => 'provides',
460  'dep' => $provide,
461  'status' => true,
462  'value' => ''
463  );
464  }
465 
466  return $report;
467  }
468 
469  return true;
470  }
471 
480  private function checkDepPlugin(array $dep, array $plugins, $inverse = false) {
481  $r = _elgg_check_plugins_provides('plugin', $dep['name'], $dep['version'], $dep['comparison']);
482 
483  if ($inverse) {
484  $r['status'] = !$r['status'];
485  }
486 
487  return $r;
488  }
489 
498  private function checkDepPriority(array $dep, array $plugins, $inverse = false) {
499  // grab the \ElggPlugin using this package.
500  $plugin_package = elgg_get_plugin_from_id($this->getID());
501  if (!$plugin_package) {
502  return array(
503  'status' => true,
504  'value' => 'uninstalled'
505  );
506  }
507 
508  $test_plugin = elgg_get_plugin_from_id($dep['plugin']);
509 
510  // If this isn't a plugin or the plugin isn't installed or active
511  // priority doesn't matter. Use requires to check if a plugin is active.
512  if (!$test_plugin || !$test_plugin->isActive()) {
513  return array(
514  'status' => true,
515  'value' => 'uninstalled'
516  );
517  }
518 
519  $plugin_priority = $plugin_package->getPriority();
520  $test_plugin_priority = $test_plugin->getPriority();
521 
522  switch ($dep['priority']) {
523  case 'before':
524  $status = $plugin_priority < $test_plugin_priority;
525  break;
526 
527  case 'after':
528  $status = $plugin_priority > $test_plugin_priority;
529  break;
530 
531  default;
532  $status = false;
533  }
534 
535  // get the current value
536  if ($plugin_priority < $test_plugin_priority) {
537  $value = 'before';
538  } else {
539  $value = 'after';
540  }
541 
542  if ($inverse) {
543  $status = !$status;
544  }
545 
546  return array(
547  'status' => $status,
548  'value' => $value
549  );
550  }
551 
560  private function checkDepElgg(array $dep, $elgg_version, $inverse = false) {
561  $status = version_compare($elgg_version, $dep['version'], $dep['comparison']);
562 
563  if ($inverse) {
564  $status = !$status;
565  }
566 
567  return array(
568  'status' => $status,
569  'value' => $elgg_version
570  );
571  }
572 
580  private function checkDepPhpVersion(array $dep, $inverse = false) {
581  $php_version = phpversion();
582  $status = version_compare($php_version, $dep['version'], $dep['comparison']);
583 
584  if ($inverse) {
585  $status = !$status;
586  }
587 
588  return array(
589  'status' => $status,
590  'value' => $php_version
591  );
592  }
593 
606  private function checkDepPhpExtension(array $dep, $inverse = false) {
607  $name = $dep['name'];
608  $version = $dep['version'];
609  $comparison = $dep['comparison'];
610 
611  // not enabled.
612  $status = extension_loaded($name);
613 
614  // enabled. check version.
615  $ext_version = phpversion($name);
616 
617  if ($status) {
618  // some extensions (like gd) don't provide versions. neat.
619  // don't check version info and return a lie.
620  if ($ext_version && $version) {
621  $status = version_compare($ext_version, $version, $comparison);
622  }
623 
624  if (!$ext_version) {
625  $ext_version = '???';
626  }
627  }
628 
629  // some php extensions can be emulated, so check provides.
630  if ($status == false) {
631  $provides = _elgg_check_plugins_provides('php_extension', $name, $version, $comparison);
632  $status = $provides['status'];
633  $ext_version = $provides['value'];
634  }
635 
636  if ($inverse) {
637  $status = !$status;
638  }
639 
640  return array(
641  'status' => $status,
642  'value' => $ext_version
643  );
644  }
645 
653  private function checkDepPhpIni($dep, $inverse = false) {
654  $name = $dep['name'];
655  $value = $dep['value'];
656  $comparison = $dep['comparison'];
657 
658  // ini_get() normalizes truthy values to 1 but falsey values to 0 or ''.
659  // version_compare() considers '' < 0, so normalize '' to 0.
660  // \ElggPluginManifest normalizes all bool values and '' to 1 or 0.
661  $setting = ini_get($name);
662 
663  if ($setting === '') {
664  $setting = 0;
665  }
666 
667  $status = version_compare($setting, $value, $comparison);
668 
669  if ($inverse) {
670  $status = !$status;
671  }
672 
673  return array(
674  'status' => $status,
675  'value' => $setting
676  );
677  }
678 
684  public function getID() {
685  return $this->id;
686  }
687 
693  public function getError() {
694  return $this->errorMsg;
695  }
696 }
$r
$plugin
if(!array_key_exists($filename, $text_files)) $file
if($guid==elgg_get_logged_in_user_guid()) $name
Definition: delete.php:21
_elgg_check_plugins_provides($type, $name, $version=null, $comparison= 'ge')
Checks if a plugin is currently providing $type and $name, and optionally checking a version...
Definition: plugins.php:182
$e
Definition: metadata.php:12
getManifest()
Returns a parsed manifest file.
checkDependencies($full_report=false)
Returns if the Elgg system meets the plugin's dependency requirements.
sanitise_filepath($path, $append_slash=true)
Sanitise file paths ensuring that they begin and end with slashes etc.
Definition: elgglib.php:399
$value
Definition: longtext.php:26
$report
$CONFIG path
The full path where Elgg is installed.
Definition: config.php:16
getTextFilenames()
Returns an array of present and readable text files.
_elgg_services(\Elgg\Di\ServiceProvider $services=null)
Get the global service provider.
Definition: autoloader.php:17
elgg_get_plugins($status= 'active', $site_guid=null)
Returns an ordered list of plugins.
Definition: plugins.php:132
__construct($plugin, $validate=true)
Load a plugin package from mod/$id or by full path.
elgg_get_version($human_readable=false)
Get the current Elgg version information.
Definition: elgglib.php:1043
getError()
Returns the last error message.
getID()
Returns the Plugin ID.
$version
Definition: version.php:14
isValid()
Checks if this is a valid Elgg plugin.
elgg_get_plugin_from_id($plugin_id)
Returns an object with the path $path.
Definition: plugins.php:82