Autodoc
  • Namespace
  • Class
  • Tree

Namespaces

  • BlueTihi
    • Context
  • Brickrouge
    • Element
      • Nodes
    • Renderer
    • Widget
  • ICanBoogie
    • ActiveRecord
    • AutoConfig
    • CLDR
    • Composer
    • Core
    • Event
    • Exception
    • HTTP
      • Dispatcher
      • Request
    • I18n
      • Translator
    • Mailer
    • Modules
      • Taxonomy
        • Support
      • Thumbnailer
        • Versions
    • Object
    • Operation
      • Dispatcher
    • Prototype
    • Routes
    • Routing
      • Dispatcher
    • Session
  • Icybee
    • ActiveRecord
      • Model
    • ConfigOperation
    • Document
    • EditBlock
    • Element
      • ActionbarContextual
      • ActionbarSearch
      • ActionbarToolbar
    • FormBlock
    • Installer
    • ManageBlock
    • Modules
      • Articles
      • Cache
        • Collection
        • ManageBlock
      • Comments
        • ManageBlock
      • Contents
        • ManageBlock
      • Dashboard
      • Editor
        • Collection
      • Files
        • File
        • ManageBlock
      • Forms
        • Form
        • ManageBlock
      • I18n
      • Images
        • ManageBlock
      • Members
      • Modules
        • ManageBlock
      • Nodes
        • ManageBlock
        • Module
      • Pages
        • BreadcrumbElement
        • LanguagesElement
        • ManageBlock
        • NavigationBranchElement
        • NavigationElement
        • Page
        • PageController
      • Registry
      • Search
      • Seo
      • Sites
        • ManageBlock
      • Taxonomy
        • Terms
          • ManageBlock
        • Vocabulary
          • ManageBlock
      • Users
        • ManageBlock
        • NonceLogin
        • Roles
      • Views
        • ActiveRecordProvider
        • Collection
        • View
    • Operation
      • ActiveRecord
      • Constructor
      • Module
      • Widget
    • Rendering
  • None
  • Patron
  • PHP

Classes

  • ActiveRecord
  • Cache
  • Configs
  • Core
  • DateTime
  • Debug
  • DeleteOperation
  • Errors
  • Event
  • EventHook
  • Events
  • FileCache
  • FormattedString
  • Helpers
  • I18n
  • Image
  • Inflections
  • Inflector
  • Models
  • Module
  • Modules
  • Object
  • Operation
  • PingOperation
  • Prototype
  • Route
  • Routes
  • SaveOperation
  • Session
  • TimeZone
  • TimeZoneLocation
  • Uploaded
  • Vars
  • VarsIterator

Interfaces

  • StorageInterface
  • ToArray
  • ToArrayRecursive

Traits

  • PrototypeTrait
  • ToArrayRecursiveTrait

Exceptions

  • AlreadyAuthenticated
  • AuthenticationRequired
  • Exception
  • ModuleConstructorMissing
  • ModuleIsDisabled
  • ModuleNotDefined
  • OffsetError
  • OffsetNotDefined
  • OffsetNotReadable
  • OffsetNotWritable
  • PermissionRequired
  • PropertyError
  • PropertyIsReserved
  • PropertyNotDefined
  • PropertyNotReadable
  • PropertyNotWritable
  • RouteNotDefined
  • SecurityException

Constants

  • TOKEN_ALPHA
  • TOKEN_ALPHA_UPCASE
  • TOKEN_NUMERIC
  • TOKEN_SYMBOL
  • TOKEN_SYMBOL_WIDE

Functions

  • array_flatten
  • array_insert
  • array_merge_recursive
  • camelize
  • capitalize
  • downcase
  • dump
  • escape
  • escape_all
  • exact_array_merge_recursive
  • excerpt
  • format
  • generate_token
  • generate_token_wide
  • generate_v4_uuid
  • get_autoconfig
  • humanize
  • hyphenate
  • log
  • log_error
  • log_info
  • log_success
  • log_time
  • normalize
  • normalize_namespace_part
  • normalize_url_path
  • pbkdf2
  • pluralize
  • remove_accents
  • shorten
  • singularize
  • sort_by_weight
  • stable_sort
  • strip_root
  • titleize
  • unaccent_compare
  • unaccent_compare_ci
  • underscore
  • upcase
  1 <?php
  2 
  3 /*
  4  * This file is part of the ICanBoogie package.
  5  *
  6  * (c) Olivier Laviale <olivier.laviale@gmail.com>
  7  *
  8  * For the full copyright and license information, please view the LICENSE
  9  * file that was distributed with this source code.
 10  */
 11 
 12 namespace ICanBoogie;
 13 
 14 use ICanBoogie\ActiveRecord\Model;
 15 
 16 /**
 17  * Modules manager.
 18  *
 19  * @property-read array $config_paths Paths of the enabled modules having a `config` directory.
 20  * @property-read array $locale_paths Paths of the enabled modules having a `locale` directory.
 21  * @property-read array $disabled_modules_descriptors Descriptors of the disabled modules.
 22  * @property-read array $enabled_modules_descriptors Descriptors of the enabled modules.
 23  * @property-read array $index Index for the modules.
 24  */
 25 class Modules extends Object implements \ArrayAccess, \IteratorAggregate
 26 {
 27     /**
 28      * Formats a SQL table name given the module id and the model id.
 29      *
 30      * @param string $module_id
 31      * @param string $model_id
 32      *
 33      * @return string
 34      */
 35     static public function format_model_name($module_id, $model_id='primary')
 36     {
 37         return preg_replace('#[^0-9,a-z,A-Z$_]#', '_', $module_id) . ($model_id == 'primary' ? '' : '__' . $model_id);
 38     }
 39 
 40     /**
 41      * The descriptors for the modules.
 42      *
 43      * @var array
 44      */
 45     public $descriptors = [];
 46 
 47     /**
 48      * The paths where modules can be found.
 49      *
 50      * @var array
 51      */
 52     protected $paths = [];
 53 
 54     /**
 55      * A cache for the modules index.
 56      *
 57      * @var Vars
 58      */
 59     protected $cache;
 60 
 61     /**
 62      * Instanciated modules.
 63      *
 64      * @var array
 65      */
 66     protected $modules = [];
 67 
 68     /**
 69      * The index for the available modules is created with the accessor object.
 70      *
 71      * @param array $paths The paths to look for modules.
 72      * @param Vars $cache The cache to use for the module indexes.
 73      */
 74     public function __construct($paths, Vars $cache=null)
 75     {
 76         $this->paths = $paths;
 77         $this->cache = $cache;
 78     }
 79 
 80     /**
 81      * Revokes constructions.
 82      *
 83      * The following properties are revoked:
 84      *
 85      * - {@link $enabled_modules_descriptors}
 86      * - {@link $disabled_modules_descriptors}
 87      * - {@link $catalog_paths}
 88      * - {@link $config_paths}
 89      *
 90      * The method is usually invoked when modules state changes, in order to reflect these
 91      * changes.
 92      */
 93     protected function revoke_constructions()
 94     {
 95         unset($this->enabled_modules_descriptors);
 96         unset($this->disabled_modules_descriptors);
 97         unset($this->catalog_paths);
 98         unset($this->config_paths);
 99     }
100 
101     /**
102      * Enables a module.
103      *
104      * @param string $id Identifier of the module.
105      */
106     public function enable($id)
107     {
108         $this->index;
109 
110         if (empty($this->descriptors[$id]))
111         {
112             return;
113         }
114 
115         $this->descriptors[$id][Module::T_DISABLED] = false;
116         $this->revoke_constructions();
117     }
118 
119     /**
120      * Disables a module.
121      *
122      * @param string $id Identifier of the module.
123      */
124     public function disable($id)
125     {
126         $this->index;
127 
128         if (empty($this->descriptors[$id]))
129         {
130             return;
131         }
132 
133         $this->descriptors[$id][Module::T_DISABLED] = true;
134         $this->revoke_constructions();
135     }
136 
137     /**
138      * Used to enable or disable a module using the specified offset as the module's id.
139      *
140      * The module is enabled or disabled by modifying the value of the {@link Module::T_DISABLED}
141      * key of the module's descriptor.
142      *
143      * @param mixed $id Identifier of the module.
144      * @param mixed $enable Status of the module: `true` for enabled, `false` for disabled.
145      */
146     public function offsetSet($id, $enable)
147     {
148         if (empty($this->descriptors[$id]))
149         {
150             return;
151         }
152 
153         $this->descriptors[$id][Module::T_DISABLED] = empty($enable);
154         $this->revoke_constructions();
155     }
156 
157     /**
158      * Checks the availability of a module.
159      *
160      * A module is considered available when its descriptor is defined, and the
161      * {@link Module::T_DISABLED} key of its descriptor is empty.
162      *
163      * Note: `empty()` will call {@link offsetGet()} to check if the value is not empty. So, unless
164      * you want to use the module you check, better check using `!isset()`, otherwise the module
165      * you check is loaded too.
166      *
167      * @param string $id Identifier of the module.
168      *
169      * @return boolean Whether or not the module is available.
170      */
171     public function offsetExists($id)
172     {
173         $descriptors = $this->descriptors;
174 
175         return (isset($descriptors[$id]) && empty($descriptors[$id][Module::T_DISABLED]));
176     }
177 
178     /**
179      * Disables a module by setting the {@link Module::T_DISABLED} key of its descriptor to `true`.
180      *
181      * The method also dismisses the {@link enabled_modules_descriptors} and
182      * {@link disabled_modules_descriptors} properties.
183      *
184      * @param string $id Identifier of the module.
185      */
186     public function offsetUnset($id)
187     {
188         if (empty($this->descriptors[$id]))
189         {
190             return;
191         }
192 
193         $this->descriptors[$id][Module::T_DISABLED] = true;
194         $this->revoke_constructions();
195     }
196 
197     /**
198      * Returns a module object.
199      *
200      * If the {@link autorun} property is `true`, the {@link Module::run()} method of the module
201      * is invoked upon its first loading.
202      *
203      * @param string $id The identifier of the module.
204      *
205      * @throws ModuleNotDefined when the requested module is not defined.
206      *
207      * @throws ModuleIsDisabled when the module is disabled.
208      *
209      * @throws Exception when the class that should be used to create its instance is not defined.
210      *
211      * @return Module
212      */
213     public function offsetGet($id)
214     {
215         if (isset($this->modules[$id]))
216         {
217             return $this->modules[$id];
218         }
219 
220         $descriptors = $this->descriptors;
221 
222         if (empty($descriptors[$id]))
223         {
224             throw new ModuleNotDefined($id);
225         }
226 
227         $descriptor = $descriptors[$id];
228 
229         if (!empty($descriptor[Module::T_DISABLED]))
230         {
231             throw new ModuleIsDisabled($id);
232         }
233 
234         $class = $descriptor[Module::T_CLASS];
235 
236         if (!class_exists($class, true))
237         {
238             throw new ModuleConstructorMissing($id, $class);
239         }
240 
241         return $this->modules[$id] = new $class($descriptor);
242     }
243 
244     /**
245      * Returns an iterator for the modules.
246      *
247      * @return \ArrayIterator
248      */
249     public function getIterator()
250     {
251         return new \ArrayIterator($this->modules);
252     }
253 
254     /**
255      * Indexes the modules found in the paths specified during construct.
256      *
257      * The index is made of an array of descriptors, an array of catalogs paths, an array of
258      * configs path, and finally an array of configs constructors.
259      *
260      * The method also creates a `DIR` constant for each module. The constant is defined in the
261      * namespace of the module e.g. `Icybee\Modules\Nodes\DIR`.
262      *
263      * @return array
264      */
265     protected function lazy_get_index()
266     {
267         if ($this->cache)
268         {
269             $key = 'cached_modules_' . md5(implode('#', $this->paths));
270             $index = $this->cache[$key];
271 
272             if (!$index)
273             {
274                 $this->cache[$key] = $index = $this->index_construct();
275             }
276         }
277         else
278         {
279             $index = $this->index_construct();
280         }
281 
282         $this->descriptors = $index['descriptors'];
283 
284         foreach ($this->descriptors as $descriptor)
285         {
286             $namespace = $descriptor[Module::T_NAMESPACE];
287             $constant = $namespace . '\DIR';
288 
289             if (!defined($constant))
290             {
291                 define($constant, $descriptor[Module::T_PATH]);
292             }
293         }
294 
295         return $index;
296     }
297 
298     /**
299      * Construct the index for the modules.
300      *
301      * The index contains the following values:
302      *
303      * - (array) descriptors: The descriptors of the modules, ordered by weight.
304      * - (array) catalogs: Absolute paths to locale catalog directories.
305      * - (array) configs: Absolute paths to config directories.
306      * - (array) classes aliases: An array of _key/value_ pairs where _key_ is the alias of a class
307      * and _value_ if the real class.
308      *
309      * @return array
310      */
311     protected function index_construct()
312     {
313         $isolated_require = function ($__file__, $__exposed__)
314         {
315             extract($__exposed__);
316 
317             return require $__file__;
318         };
319 
320         $descriptors = $this->paths ? $this->index_descriptors($this->paths) : [];
321         $catalogs = [];
322         $configs = [];
323 
324         foreach ($descriptors as $id => $descriptor)
325         {
326             $path = $descriptor[Module::T_PATH];
327 
328             if ($descriptor['__has_locale'])
329             {
330                 $catalogs[] = $path . 'locale';
331             }
332 
333             if ($descriptor['__has_config'])
334             {
335                 $configs[] = $path . 'config';
336             }
337         }
338 
339         return [
340 
341             'descriptors' => $descriptors,
342             'catalogs' => $catalogs,
343             'configs' => $configs
344 
345         ];
346     }
347 
348     /**
349      * Indexes descriptors.
350      *
351      * The descriptors are extended with the following default values:
352      *
353      * - (string) category: null.
354      * - (string) class: Modules\<normalized_module_part>
355      * - (string) description: null.
356      * - (bool) disabled: false if required, true otherwise.
357      * - (string) extends: null.
358      * - (string) id: The module's identifier.
359      * - (array) models: Empty array.
360      * - (string) path: The absolute path to the module directory.
361      * - (string) permission: null.
362      * - (array) permissions: Empty array.
363      * - (bool) startup: false.
364      * - (bool) required: false.
365      * - (array) requires: Empty array.
366      * - (string) weight: 0.
367      *
368      * The descriptors are ordered according to their inheritance and weight.
369      *
370      * @param array $paths
371      *
372      * @throws Exception when a directory fails to open.
373      *
374      * @return array[string]array
375      */
376     protected function index_descriptors(array $paths)
377     {
378         $descriptors = $this->collect_descriptors($paths);
379 
380         if (!$descriptors)
381         {
382             return [];
383         }
384 
385         #
386         # Compute inheritance.
387         #
388 
389         $find_parents = function($id, &$parents=[]) use (&$find_parents, &$descriptors)
390         {
391             $parent = $descriptors[$id][Module::T_EXTENDS];
392 
393             if ($parent)
394             {
395                 $parents[] = $parent;
396 
397                 $find_parents($parent, $parents);
398             }
399 
400             return $parents;
401         };
402 
403         foreach ($descriptors as $id => &$descriptor)
404         {
405             $descriptor['__parents'] = $find_parents($id);
406         }
407 
408         #
409         # Orders descriptors according to their weight.
410         #
411 
412         $ordered_ids = $this->order_ids(array_keys($descriptors), $descriptors);
413         $descriptors = array_merge(array_combine($ordered_ids, $ordered_ids), $descriptors);
414 
415         foreach ($descriptors as $id => &$descriptor)
416         {
417             foreach ($descriptor[Module::T_MODELS] as $model_id => &$model_descriptor)
418             {
419                 if ($model_descriptor == 'inherit')
420                 {
421                     $parent_descriptor = $descriptors[$descriptor[Module::T_EXTENDS]];
422                     $model_descriptor = $parent_descriptor[Module::T_MODELS][$model_id];
423                 }
424             }
425 
426             $descriptor = $this->alter_descriptor($descriptor);
427         }
428 
429         return $descriptors;
430     }
431 
432     protected function collect_descriptors(array $paths)
433     {
434         $descriptors = [];
435 
436         foreach ($paths as $root)
437         {
438             $root = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
439             $descriptor_path = $root . 'descriptor.php';
440 
441             if (file_exists($descriptor_path))
442             {
443                 $id = basename(realpath($root));
444                 $descriptor = $this->read_descriptor($id, $root);
445 
446                 $descriptors[$descriptor[Module::T_ID]] = $descriptor;
447             }
448             else
449             {
450                 try
451                 {
452                     $dir = new \DirectoryIterator($root);
453                 }
454                 catch (\Exception $e)
455                 {
456                     throw new Exception('Unable to open directory %root', [ 'root' => $root ]);
457                 }
458 
459                 foreach ($dir as $file)
460                 {
461                     if ($file->isDot() || !$file->isDir())
462                     {
463                         continue;
464                     }
465 
466                     $id = $file->getFilename();
467                     $path = $root . $id . DIRECTORY_SEPARATOR;
468                     $descriptor = $this->read_descriptor($id, $path);
469 
470                     $descriptors[$descriptor[Module::T_ID]] = $descriptor;
471                 }
472             }
473         }
474 
475         return $descriptors;
476     }
477 
478     /**
479      * Reads the descriptor file.
480      *
481      * The descriptor file is extended with private values and default values.
482      *
483      * @param string $id The identifier of the module.
484      * @param string $path The path to the directory where the descriptor is located.
485      * @throws \InvalidArgumentException in the following situations:
486      * - The descriptor is not an array
487      * - The {@link T_TITLE} key is empty.
488      * - The {@link T_NAMESPACE} key is empty.
489      *
490      * @return array
491      */
492     protected function read_descriptor($id, $path)
493     {
494         $descriptor_path = $path . 'descriptor.php';
495         $descriptor = require $descriptor_path;
496 
497         if (!is_array($descriptor))
498         {
499             throw new \InvalidArgumentException(format
500             (
501                 '%var should be an array: %type given instead in %path', [
502 
503                     'var' => 'descriptor',
504                     'type' => gettype($descriptor),
505                     'path' => strip_root($descriptor_path)
506 
507                 ]
508             ));
509         }
510 
511         if (empty($descriptor[Module::T_TITLE]))
512         {
513             throw new \InvalidArgumentException(format
514             (
515                 'The %name value of the %id module descriptor is empty in %path.', [
516 
517                     'name' => Module::T_TITLE,
518                     'id' => $id,
519                     'path' => strip_root($descriptor_path)
520 
521                 ]
522             ));
523         }
524 
525         if (empty($descriptor[Module::T_NAMESPACE]))
526         {
527             throw new \InvalidArgumentException(format
528             (
529                 '%name is required. Invalid descriptor for module %id in %path.', [
530 
531                     'name' => Module::T_NAMESPACE,
532                     'id' => $id,
533                     'path' => strip_root($descriptor_path)
534 
535                 ]
536             ));
537         }
538 
539         /*TODO-20120108: activate version checking
540         if (empty($descriptor[Module::T_VERSION]))
541         {
542             throw new Exception
543             (
544                 'The %name value of the %id module descriptor is empty in %path.', [
545 
546                     'name' => Module::T_VERSION,
547                     'id' => $id,
548                     'path' => $descriptor_path
549 
550                 ]
551             );
552         }
553         */
554 
555         return $descriptor + [
556 
557             Module::T_CATEGORY => null,
558             Module::T_CLASS => $descriptor[Module::T_NAMESPACE] . '\Module',
559             Module::T_DESCRIPTION => null,
560             Module::T_DISABLED => false,
561             Module::T_EXTENDS => null,
562             Module::T_ID => $id,
563             Module::T_MODELS => [],
564             Module::T_PATH => $path,
565             Module::T_PERMISSION => null,
566             Module::T_PERMISSIONS => [],
567             Module::T_REQUIRED => false,
568             Module::T_REQUIRES => [],
569             Module::T_VERSION => 'dev',
570             Module::T_WEIGHT => 0,
571 
572             '__has_config' => is_dir($path . 'config'),
573             '__has_locale' => is_dir($path . 'locale'),
574             '__parents' => []
575 
576         ];
577     }
578 
579     /**
580      * Alters the module descriptor.
581      *
582      * @param array $descriptor Descriptor of the module to index.
583      *
584      * @return array The altered descriptor.
585      */
586     protected function alter_descriptor(array $descriptor)
587     {
588         $id = $descriptor[Module::T_ID];
589         $path = $descriptor[Module::T_PATH];
590         $namespace = $descriptor[Module::T_NAMESPACE];
591 
592         # models and active records
593 
594         foreach ($descriptor[Module::T_MODELS] as $model_id => &$definition)
595         {
596             if (!is_array($definition))
597             {
598                 throw new \InvalidArgumentException(format
599                 (
600                     'Model definition must be array, given: %value.', [ 'value' => $definition ]
601                 ));
602             }
603 
604             $basename = $id;
605             $separator_position = strrpos($basename, '.');
606 
607             if ($separator_position)
608             {
609                 $basename = substr($basename, $separator_position + 1);
610             }
611 
612             if (empty($definition[Model::NAME]))
613             {
614                 $definition[Model::NAME] = self::format_model_name($id, $model_id);
615             }
616 
617             if (empty($definition[Model::CLASSNAME]))
618             {
619                 $definition[Model::CLASSNAME] = $namespace . '\\' . ($model_id == 'primary' ? 'Model' : camelize(singularize($model_id)) . 'Model');
620             }
621 
622             if (empty($definition[Model::ACTIVERECORD_CLASS]))
623             {
624                 $definition[Model::ACTIVERECORD_CLASS] = $namespace . '\\' . camelize(singularize($model_id == 'primary' ? $basename : $model_id));
625             }
626         }
627 
628         return $descriptor;
629     }
630 
631     /**
632      * Traverses the descriptors and create two array of descriptors: one for the disabled modules
633      * and the other for enabled modules. The {@link $disabled_modules_descriptors} magic property
634      * receives the descriptors of the disabled modules, while the {@link $enabled_modules_descriptors}
635      * magic property receives the descriptors of the enabled modules.
636      */
637     private function sort_modules_descriptors()
638     {
639         $enabled = [];
640         $disabled = [];
641 
642         $this->index; // we make sure that the modules were indexed
643 
644         foreach ($this->descriptors as $id => &$descriptor)
645         {
646             if (isset($this[$id]))
647             {
648                 $enabled[$id] = $descriptor;
649             }
650             else
651             {
652                 $disabled[$id] = $descriptor;
653             }
654         }
655 
656         $this->enabled_modules_descriptors = $enabled;
657         $this->disabled_modules_descriptors = $disabled;
658     }
659 
660     /**
661      * Returns the descriptors of the disabled modules.
662      *
663      * This method is the getter for the {@link $disabled_modules_descriptors} magic property.
664      *
665      * @return array
666      */
667     protected function lazy_get_disabled_modules_descriptors()
668     {
669         $this->sort_modules_descriptors();
670 
671         return $this->disabled_modules_descriptors;
672     }
673 
674     /**
675      * Returns the descriptors of the enabled modules.
676      *
677      * This method is the getter for the {@link $enabled_modules_descriptors} magic property.
678      *
679      * @return array
680      */
681     protected function lazy_get_enabled_modules_descriptors()
682     {
683         $this->sort_modules_descriptors();
684 
685         return $this->enabled_modules_descriptors;
686     }
687 
688     /**
689      * Returns the paths of the enabled modules which have a `locale` folder.
690      *
691      * @return array[]string
692      */
693     protected function lazy_get_locale_paths()
694     {
695         $paths = [];
696 
697         foreach ($this->enabled_modules_descriptors as $module_id => $descriptor)
698         {
699             if (!$descriptor['__has_locale'])
700             {
701                 continue;
702             }
703 
704             $paths[] = $descriptor[Module::T_PATH] . 'locale';
705         }
706 
707         return $paths;
708     }
709 
710     /**
711      * Returns the paths of the enabled modules which have a `config` folder.
712      *
713      * @return array[]string
714      */
715     protected function lazy_get_config_paths()
716     {
717         $paths = [];
718 
719         foreach ($this->enabled_modules_descriptors as $module_id => $descriptor)
720         {
721             if (!$descriptor['__has_config'])
722             {
723                 continue;
724             }
725 
726             $paths[] = $descriptor[Module::T_PATH] . 'config';
727         }
728 
729         return $paths;
730     }
731 
732     /**
733      * Orders the module ids provided according to module inheritance and weight.
734      *
735      * @param array $ids The module ids to order.
736      * @param array $descriptors Module descriptors.
737      *
738      * @return array
739      */
740     public function order_ids(array $ids, array $descriptors=null)
741     {
742         $ordered = [];
743         $extends_weight = [];
744 
745         if ($descriptors === null)
746         {
747             $descriptors = $this->descriptors;
748         }
749 
750         $count_extends = function($super_id) use (&$count_extends, &$descriptors)
751         {
752             $i = 0;
753 
754             foreach ($descriptors as $id => $descriptor)
755             {
756                 if ($descriptor[Module::T_EXTENDS] !== $super_id)
757                 {
758                     continue;
759                 }
760 
761                 $i += 1 + $count_extends($id);
762             }
763 
764             return $i;
765         };
766 
767         $count_required = function($required_id) use (&$descriptors, &$extends_weight)
768         {
769             $i = 0;
770 
771             foreach ($descriptors as $id => $descriptor)
772             {
773                 if (empty($descriptor[Module::T_REQUIRES][$required_id]))
774                 {
775                     continue;
776                 }
777 
778                 $i += 1 + $extends_weight[$id];
779             }
780 
781             return $i;
782         };
783 
784         foreach ($ids as $id)
785         {
786             $extends_weight[$id] = $count_extends($id);
787         }
788 
789         foreach ($ids as $id)
790         {
791             $ordered[$id] = -$extends_weight[$id] -$count_required($id) + $descriptors[$id][Module::T_WEIGHT];
792         }
793 
794         stable_sort($ordered);
795 
796         return array_keys($ordered);
797     }
798 
799     /**
800      * Returns the usage of a module by other modules.
801      *
802      * @param string $module_id The identifier of the module.
803      * @param bool $all The usage is only computed for enabled module, this parameter can be used
804      * to compute the usage with disabled modules also.
805      *
806      * @return int
807      */
808     public function usage($module_id, $all=false)
809     {
810         $n = 0;
811 
812         foreach ($this->descriptors as $m_id => $descriptor)
813         {
814             if (!$all && !isset($this[$m_id]))
815             {
816                 continue;
817             }
818 
819             if ($descriptor[Module::T_EXTENDS] == $module_id)
820             {
821                 $n++;
822             }
823 
824             if (!empty($descriptor[Module::T_REQUIRES][$module_id]))
825             {
826                 $n++;
827             }
828         }
829 
830         return $n;
831     }
832 
833     /**
834      * Checks if a module extends another.
835      *
836      * @param string $module_id Module identifier.
837      * @param string $extending_id Identifier of the extended module.
838      *
839      * @return boolean `true` if the module extends the other.
840      */
841     public function is_extending($module_id, $extending_id)
842     {
843         while ($module_id)
844         {
845             if ($module_id == $extending_id)
846             {
847                 return true;
848             }
849 
850             $descriptor = $this->descriptors[$module_id];
851 
852             $module_id = isset($descriptor[Module::T_EXTENDS]) ? $descriptor[Module::T_EXTENDS] : null;
853         }
854 
855         return false;
856     }
857 }
858 
859 /*
860  * EXCEPTIONS
861  */
862 
863 /**
864  * Exception thrown when a disabled module is requested.
865  *
866  * @property-read string $module_id The identifier of the disabled module.
867  */
868 class ModuleIsDisabled extends \RuntimeException
869 {
870     private $module_id;
871 
872     public function __construct($module_id, $code=500, \Exception $previous=null)
873     {
874         $this->module_id = $module_id;
875 
876         parent::__construct(format('Module is disabled: %module_id', [ 'module_id' => $module_id ]), $code, $previous);
877     }
878 
879     public function __get($property)
880     {
881         if ($property == 'module_id')
882         {
883             return $this->module_id;
884         }
885 
886         throw new PropertyNotDefined([ $property, $this ]);
887     }
888 }
889 
890 /**
891  * Exception thrown when a requested module is not defined.
892  *
893  * @property-read string $module_id The identifier of the module that is not defined.
894  */
895 class ModuleNotDefined extends \RuntimeException
896 {
897     /**
898      * Identifier of the module.
899      *
900      * @var string
901      */
902     private $module_id;
903 
904     public function __construct($module_id, $code=500, \Exception $previous=null)
905     {
906         $this->module_id = $module_id;
907 
908         parent::__construct(format('Module is not defined: %module_id', [ 'module_id' => $module_id ]), $code, $previous);
909     }
910 
911     public function __get($property)
912     {
913         if ($property == 'module_id')
914         {
915             return $this->module_id;
916         }
917 
918         throw new PropertyNotDefined([ $property, $this ]);
919     }
920 }
921 
922 /**
923  * Exception thrown when a class is missing to instantiate a module.
924  *
925  * @property-read string $module_id The identifier of the module.
926  * @property-read string $class The name of the missing class.
927  */
928 class ModuleConstructorMissing extends \RuntimeException
929 {
930     /**
931      * Identifier of the module.
932      *
933      * @var string
934      */
935     private $module_id;
936 
937     /**
938      * Class name of the module.
939      *
940      * @var string
941      */
942     private $class;
943 
944     public function __construct($module_id, $class, $code=500, \Exception $previous=null)
945     {
946         $this->module_id = $module_id;
947         $this->class = $class;
948 
949         parent::__construct(format('Missing class %class to instantiate module %id.', [ 'class' => $class, 'id' => $module_id ]));
950     }
951 
952     public function __get($property)
953     {
954         if ($property == 'module_id')
955         {
956             return $this->module_id;
957         }
958         else if ($property == 'class')
959         {
960             return $this->class;
961         }
962 
963         throw new PropertyNotDefined([ $property, $this ]);
964     }
965 }
Autodoc API documentation generated by ApiGen 2.8.0