Passed
Push — master ( 52d6d0...ade603 )
by Andreas
33:03
created

midcom_helper__componentloader::get_manifests()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 6
nop 1
dl 0
loc 18
ccs 0
cts 10
cp 0
crap 20
rs 9.9666
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package midcom.helper
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
/**
10
 * This class is a Factory that is responsible for loading and
11
 * establishing the interface to a MidCOM Component.
12
 *
13
 * <b>Working with components</b>
14
 *
15
 * Normally, two things are important when you deal with other components:
16
 *
17
 * First, if you want to list other components, or for example check whether they
18
 * are available, you should use the component manifest listing, known as $manifests.
19
 * It gives you all meta-information about the components.
20
 *
21
 * This should actually suffice for most normal operations.
22
 *
23
 * If you develop framework tools (like administration interfaces), you will also
24
 * need access to the component interface class, which can be obtained by
25
 * get_interface_class(). This class is derived from the component interface
26
 * baseclass and should give you everything you need to work with the component
27
 * and its information itself.
28
 *
29
 * Other than that, you should not have to deal with the components, perhaps with
30
 * the only exception of is_loaded() and load() to ensure other components are loaded
31
 * in case you need them and they are not a pure-code library.
32
 *
33
 * <b>Loading components</b>
34
 *
35
 * When the component loader receives a request it roughly works in
36
 * three stages:
37
 *
38
 * 1. Verify that the given component is valid in terms of the MidCOM Specification.
39
 * 2. Initialize the Component. Check whether all required concept classes exist.
40
 * 3. Return the various interface concepts upon each request
41
 *    from the framework.
42
 *
43
 * Stage 1 will do all basic sanity checking. If anything is missing, step 1
44
 * fails and the componentloader refuses to load the component.
45
 *
46
 * Stage 2 will then load the interfaces.php file from the midcom
47
 * directory. The existence of all required Interface classes is
48
 * then checked. If this check is successful, the concrete classes
49
 * of the various interface concepts are instantiated and stored
50
 * internally. The component is initialized by the call to
51
 * initialize() which should load everything necessary.
52
 *
53
 * Stage 3 is the final stage where the loader stays in memory in
54
 * order to return the loaded component's Interface instances upon request.
55
 *
56
 * In case you need an instance of the component loader to verify or
57
 * transform component paths, use midcom::get()->componentloader
58
 *
59
 * @package midcom.helper
60
 */
61
class midcom_helper__componentloader
62
{
63
    /**
64
     * This array contains a list of components that were tried to be loaded.
65
     * The components are added to this list *even* if the system only tried
66
     * to load it and failed. This way we protect against duplicate class errors
67
     * and the like if a defective class is tried to be loaded twice.
68
     *
69
     * The array maps component names to loading results. The loading result is
70
     * either false or true as per the result of the load call.
71
     *
72
     * @var array
73
     */
74
    private $_tried_to_load = [];
75
76
    /**
77
     * This stores the interface instances of the different loaded components,
78
     * indexed by their MidCOM Path.
79
     *
80
     * @var midcom_baseclasses_components_interface[]
81
     */
82
    private $_interface_classes = [];
83
84
    /**
85
     * This lists all available components in the systems in the form of their manifests,
86
     * indexed by the component name. Whenever possible you should refer to this listing
87
     * to gain information about the components available.
88
     *
89
     * This information is loaded during startup.
90
     *
91
     * @var midcom_core_manifest[]
92
     */
93
    public $manifests = [];
94
95
    /**
96
     * Invoke _load directly. If the loading process is unsuccessful, throw midcom_error.
97
     *
98
     * @param string $path    The component to load explicitly.
99
     */
100 12
    public function load($path)
101
    {
102 12
        if (!$this->_load($path)) {
103 1
            throw new midcom_error("Failed to load the component {$path}, see the debug log for more information");
104
        }
105 11
    }
106
107
    /**
108
     * Invoke _load directly. If the loading process is unsuccessful, false is returned.
109
     *
110
     * @param string $path    The component to load explicitly.
111
     * @return boolean Indicating success.
112
     */
113 16
    public function load_graceful($path) : bool
114
    {
115 16
        return $this->_load($path);
116
    }
117
118
    /**
119
     * This will load the pure-code library denoted by $path. It will
120
     * return true if the component truly was a pure-code library, false otherwise.
121
     * If the component loader cannot load the component, midcom_error will be
122
     * thrown.
123
     *
124
     * Common example:
125
     *
126
     * <code>
127
     * midcom::get()->componentloader->load_library('org.openpsa.httplib');
128
     * </code>
129
     *
130
     * @param string $path    The name of the code library to load.
131
     * @return boolean            Indicates whether the library was successfully loaded.
132
     */
133
    public function load_library($path) : bool
134
    {
135
        if (!array_key_exists($path, $this->manifests)) {
136
            debug_add("Cannot load component {$path} as library, it is not installed.", MIDCOM_LOG_ERROR);
137
            return false;
138
        }
139
140
        if (!$this->manifests[$path]->purecode) {
141
            debug_add("Cannot load component {$path} as library, it is a full-fledged component.", MIDCOM_LOG_ERROR);
142
            debug_print_r('Manifest:', $this->manifests[$path]);
143
            return false;
144
        }
145
146
        $this->load($path);
147
148
        return true;
149
    }
150
151
    /**
152
     * Load the component specified by $path. If the component could not be loaded
153
     * successfully due to integrity errors, it will return false.
154
     *
155
     * @param string $path    The component to load.
156
     * @return boolean Indicating success.
157
     */
158 28
    private function _load($path) : bool
159
    {
160 28
        if (empty($path)) {
161
            debug_add("No component path given, aborting");
162
            return false;
163
        }
164
165
        // Check if this component is already loaded...
166 28
        if (array_key_exists($path, $this->_tried_to_load)) {
167 14
            debug_add("Component {$path} already loaded.");
168 14
            return $this->_tried_to_load[$path];
169
        }
170
171
        // Flag this path as loaded/failed, we'll set this flag to true when we reach
172
        // the end of this call.
173 14
        $this->_tried_to_load[$path] = false;
174
175
        // Check if the component is listed in the class manifest list. If not,
176
        // we immediately bail - anything went wrong while loading the component
177
        // (f.x. broken DBA classes).
178 14
        if (!array_key_exists($path, $this->manifests)) {
179 2
            debug_add("The component {$path} was not found in the manifest list. Cannot load it.",
180 2
                MIDCOM_LOG_WARN);
181 2
            return false;
182
        }
183
184 13
        $classname = midcom_baseclasses_components_interface::get_classname($path);
185 13
        $this->_interface_classes[$path] = new $classname;
186 13
        $this->_interface_classes[$path]->initialize($path);
187 13
        $this->_tried_to_load[$path] = true;
188
189 13
        return true;
190
    }
191
192
    /**
193
     * Returns true if the component identified by the MidCOM path $url
194
     * is already loaded and available for usage.
195
     *
196
     * @param string $path    The component to be queried.
197
     * @return boolean            true if it is loaded, false otherwise.
198
     */
199 451
    public function is_loaded($path) : bool
200
    {
201 451
        if ($path == 'midcom') {
202
            // MidCOM is "always loaded"
203 1
            return true;
204
        }
205 451
        return array_key_exists($path, $this->_interface_classes);
206
    }
207
208
    /**
209
     * Returns true if the component identified by the MidCOM path $url
210
     * is installed.
211
     *
212
     * @param string $path    The component to be queried.
213
     * @return boolean            true if it is loaded, false otherwise.
214
     */
215 165
    public function is_installed($path) : bool
216
    {
217 165
        if (empty($this->manifests)) {
218
            $this->load_all_manifests();
219
        }
220 165
        return array_key_exists($path, $this->manifests);
221
    }
222
223 1
    public function register_component($name, $path)
224
    {
225 1
        $filename = "{$path}/config/manifest.inc";
226 1
        if (!file_exists($filename)) {
227
            throw new midcom_error('Manifest not found for ' . $name);
228
        }
229 1
        if (empty($this->manifests)) {
230
            $this->load_all_manifests();
231
        }
232 1
        $this->_register_manifest(new midcom_core_manifest($filename));
233 1
    }
234
235
    /**
236
     * Returns an instance of the specified component's
237
     * interface class. The component is given in $path as a MidCOM path.
238
     * Such an instance will be cached by the framework so that only
239
     * one instance is always active for each component. Missing
240
     * components will be dynamically loaded into memory.
241
     *
242
     * @param string $path    The component name.
243
     */
244 449
    public function get_interface_class($path) : midcom_baseclasses_components_interface
245
    {
246 449
        if (!$this->is_loaded($path)) {
247 8
            $this->load($path);
248
        }
249
250 449
        return $this->_interface_classes[$path];
251
    }
252
253
    /**
254
     * Convert a component path (net.nehmer.blog) to a snippetpath (/net/nehmer/blog).
255
     *
256
     * @param string $component_name    Input string.
257
     * @return string        Converted string.
258
     */
259 509
    public function path_to_snippetpath($component_name)
260
    {
261 509
        if (array_key_exists($component_name, $this->manifests)) {
262 509
            return dirname($this->manifests[$component_name]->filename, 2);
263
        }
264 1
        debug_add("Component {$component_name} is not registered", MIDCOM_LOG_CRIT);
265 1
        return false;
266
    }
267
268
    /**
269
     * Convert a component path (net.nehmer.blog) to a class prefix (net_nehmer_blog).
270
     *
271
     * @param string $path    Input string.
272
     */
273 350
    public function path_to_prefix($path) : string
274
    {
275 350
        return strtr($path, ".", "_");
276
    }
277
278
    /**
279
     * Retrieve a list of all loaded components. The Array will contain an
280
     * unsorted collection of MidCOM Paths.
281
     */
282 1
    public function list_loaded_components() : array
283
    {
284 1
        return array_keys($this->_interface_classes);
285
    }
286
287
    /**
288
     * This function is called during system startup and loads all component manifests. The list
289
     * of manifests to load is determined using a find shell call and is cached using the memcache
290
     * cache module.
291
     *
292
     * This method is executed during system startup by the framework. Other parts of the system
293
     * must not access it.
294
     */
295 12
    public function load_all_manifests()
296
    {
297 12
        $manifests = midcom::get()->cache->memcache->get('MISC', 'midcom.componentloader.manifests');
0 ignored issues
show
Bug introduced by
The property memcache does not seem to exist on midcom_services_cache.
Loading history...
298
299 12
        if (!is_array($manifests)) {
300
            debug_add('Cache miss, generating component manifest cache now.');
301
            $manifests = $this->get_manifests(midcom::get()->config);
302
            midcom::get()->cache->memcache->put('MISC', 'midcom.componentloader.manifests', $manifests);
303
        }
304 12
        array_map([$this, '_register_manifest'], $manifests);
305 12
    }
306
307
    /**
308
     * This function is called from the class manifest loader in case of a cache miss.
309
     *
310
     * @param midcom_config $config The configuration object
311
     */
312
    public function get_manifests(midcom_config $config) : array
313
    {
314
        $manifests = [];
315
316
        foreach ($config->get('builtin_components', []) as $path) {
317
            $manifests[] = new midcom_core_manifest(dirname(MIDCOM_ROOT) . '/' . $path . '/config/manifest.inc');
318
        }
319
320
        // now we look for extra components the user may have registered
321
        foreach ($config->get('midcom_components', []) as $path) {
322
            if (!file_exists($path . '/config/manifest.inc')) {
323
                debug_add('No manifest found in path ' . $path . ', skipping', MIDCOM_LOG_ERROR);
324
                continue;
325
            }
326
            $manifests[] = new midcom_core_manifest($path . '/config/manifest.inc');
327
        }
328
329
        return $manifests;
330
    }
331
332
    /**
333
     * Register manifest data.
334
     *
335
     * All default privileges are made known to ACL, the watches are registered
336
     *
337
     * @param midcom_core_manifest $manifest the manifest object to load.
338
     */
339 13
    private function _register_manifest(midcom_core_manifest $manifest)
340
    {
341 13
        $this->manifests[$manifest->name] = $manifest;
342
343
        // Register Privileges
344 13
        midcom::get()->auth->acl->register_default_privileges($manifest->privileges);
345
346
        // Register watches
347 13
        if ($manifest->watches !== null) {
348 12
            midcom::get()->dispatcher->add_watches($manifest->watches, $manifest->name);
349
        }
350 13
    }
351
352
    /**
353
     * Build a complete set of custom data associated with a given component
354
     * identifier.
355
     *
356
     * @param string $component The custom data component index to look for.
357
     */
358 13
    public function get_all_manifest_customdata($component) : array
359
    {
360 13
        $result = [];
361 13
        foreach ($this->manifests as $manifest) {
362 13
            if (array_key_exists($component, $manifest->customdata)) {
363 13
                $result[$manifest->name] = $manifest->customdata[$component];
364
            }
365
        }
366 13
        return $result;
367
    }
368
369 2
    public function get_component_icon($component, $provide_fallback = true)
370
    {
371 2
        if (!$this->is_installed($component)) {
372
            return null;
373
        }
374
375 2
        if (!empty($this->manifests[$component]->icon)) {
376 2
            return $this->manifests[$component]->icon;
377
        }
378
379 1
        if (!$provide_fallback) {
380
            return null;
381
        }
382
383 1
        return 'puzzle-piece';
384
    }
385
}
386