Elgg  Version 1.11
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_version', '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_version':
407  elgg_deprecated_notice("elgg_version in manifest.xml files is deprecated. Use elgg_release", 1.9);
408  $result = $this->checkDepElgg($dep, elgg_get_version(), $inverse);
409  break;
410 
411  case 'elgg_release':
412  $result = $this->checkDepElgg($dep, elgg_get_version(true), $inverse);
413  break;
414 
415  case 'plugin':
416  $result = $this->checkDepPlugin($dep, $enabled_plugins, $inverse);
417  break;
418 
419  case 'priority':
420  $result = $this->checkDepPriority($dep, $enabled_plugins, $inverse);
421  break;
422 
423  case 'php_version':
424  $result = $this->checkDepPhpVersion($dep, $inverse);
425  break;
426 
427  case 'php_extension':
428  $result = $this->checkDepPhpExtension($dep, $inverse);
429  break;
430 
431  case 'php_ini':
432  $result = $this->checkDepPhpIni($dep, $inverse);
433  break;
434 
435  default:
436  $result = null;//skip further check
437  break;
438  }
439 
440  if ($result !== null) {
441  // unless we're doing a full report, break as soon as we fail.
442  if (!$full_report && !$result['status']) {
443  $this->errorMsg = "Missing dependencies.";
444  return $result['status'];
445  } else {
446  // build report element and comment
447  $report[] = array(
448  'type' => $dep_type,
449  'dep' => $dep,
450  'status' => $result['status'],
451  'value' => $result['value']
452  );
453  }
454  }
455  }
456  }
457 
458  if ($full_report) {
459  // add provides to full report
460  $provides = $this->getManifest()->getProvides();
461 
462  foreach ($provides as $provide) {
463  $report[] = array(
464  'type' => 'provides',
465  'dep' => $provide,
466  'status' => true,
467  'value' => ''
468  );
469  }
470 
471  return $report;
472  }
473 
474  return true;
475  }
476 
485  private function checkDepPlugin(array $dep, array $plugins, $inverse = false) {
486  $r = _elgg_check_plugins_provides('plugin', $dep['name'], $dep['version'], $dep['comparison']);
487 
488  if ($inverse) {
489  $r['status'] = !$r['status'];
490  }
491 
492  return $r;
493  }
494 
503  private function checkDepPriority(array $dep, array $plugins, $inverse = false) {
504  // grab the \ElggPlugin using this package.
505  $plugin_package = elgg_get_plugin_from_id($this->getID());
506  $plugin_priority = $plugin_package->getPriority();
507  $test_plugin = elgg_get_plugin_from_id($dep['plugin']);
508 
509  // If this isn't a plugin or the plugin isn't installed or active
510  // priority doesn't matter. Use requires to check if a plugin is active.
511  if (!$plugin_package || !$test_plugin || !$test_plugin->isActive()) {
512  return array(
513  'status' => true,
514  'value' => 'uninstalled'
515  );
516  }
517 
518  $test_plugin_priority = $test_plugin->getPriority();
519 
520  switch ($dep['priority']) {
521  case 'before':
522  $status = $plugin_priority < $test_plugin_priority;
523  break;
524 
525  case 'after':
526  $status = $plugin_priority > $test_plugin_priority;
527  break;
528 
529  default;
530  $status = false;
531  }
532 
533  // get the current value
534  if ($plugin_priority < $test_plugin_priority) {
535  $value = 'before';
536  } else {
537  $value = 'after';
538  }
539 
540  if ($inverse) {
541  $status = !$status;
542  }
543 
544  return array(
545  'status' => $status,
546  'value' => $value
547  );
548  }
549 
558  private function checkDepElgg(array $dep, $elgg_version, $inverse = false) {
559  $status = version_compare($elgg_version, $dep['version'], $dep['comparison']);
560 
561  if ($inverse) {
562  $status = !$status;
563  }
564 
565  return array(
566  'status' => $status,
567  'value' => $elgg_version
568  );
569  }
570 
578  private function checkDepPhpVersion(array $dep, $inverse = false) {
579  $php_version = phpversion();
580  $status = version_compare($php_version, $dep['version'], $dep['comparison']);
581 
582  if ($inverse) {
583  $status = !$status;
584  }
585 
586  return array(
587  'status' => $status,
588  'value' => $php_version
589  );
590  }
591 
604  private function checkDepPhpExtension(array $dep, $inverse = false) {
605  $name = $dep['name'];
606  $version = $dep['version'];
607  $comparison = $dep['comparison'];
608 
609  // not enabled.
610  $status = extension_loaded($name);
611 
612  // enabled. check version.
613  $ext_version = phpversion($name);
614 
615  if ($status) {
616  // some extensions (like gd) don't provide versions. neat.
617  // don't check version info and return a lie.
618  if ($ext_version && $version) {
619  $status = version_compare($ext_version, $version, $comparison);
620  }
621 
622  if (!$ext_version) {
623  $ext_version = '???';
624  }
625  }
626 
627  // some php extensions can be emulated, so check provides.
628  if ($status == false) {
629  $provides = _elgg_check_plugins_provides('php_extension', $name, $version, $comparison);
630  $status = $provides['status'];
631  $ext_version = $provides['value'];
632  }
633 
634  if ($inverse) {
635  $status = !$status;
636  }
637 
638  return array(
639  'status' => $status,
640  'value' => $ext_version
641  );
642  }
643 
651  private function checkDepPhpIni($dep, $inverse = false) {
652  $name = $dep['name'];
653  $value = $dep['value'];
654  $comparison = $dep['comparison'];
655 
656  // ini_get() normalizes truthy values to 1 but falsey values to 0 or ''.
657  // version_compare() considers '' < 0, so normalize '' to 0.
658  // \ElggPluginManifest normalizes all bool values and '' to 1 or 0.
659  $setting = ini_get($name);
660 
661  if ($setting === '') {
662  $setting = 0;
663  }
664 
665  $status = version_compare($setting, $value, $comparison);
666 
667  if ($inverse) {
668  $status = !$status;
669  }
670 
671  return array(
672  'status' => $status,
673  'value' => $setting
674  );
675  }
676 
682  public function getID() {
683  return $this->id;
684  }
685 
691  public function getError() {
692  return $this->errorMsg;
693  }
694 }
$r
$plugin
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:264
$e
Definition: metadata.php:12
getManifest()
Returns a parsed manifest file.
checkDependencies($full_report=false)
Returns if the Elgg system meets the plugin&#39;s dependency requirements.
sanitise_filepath($path, $append_slash=true)
Sanitise file paths ensuring that they begin and end with slashes etc.
Definition: elgglib.php:368
$value
Definition: longtext.php:26
$report
$CONFIG path
The full path where Elgg is installed.
Definition: config.php:66
getTextFilenames()
Returns an array of present and readable text files.
$plugins
_elgg_services()
Definition: autoloader.php:14
elgg_deprecated_notice($msg, $dep_version, $backtrace_level=1)
Log a notice about deprecated use of a function, view, etc.
Definition: elgglib.php:1006
elgg_get_plugins($status= 'active', $site_guid=null)
Returns an ordered list of plugins.
Definition: plugins.php:162
__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:976
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:97