Completed
Push — master ( 0021de...b72ee8 )
by Andreas
17:13
created

midcom_baseclasses_components_viewer::show()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 2
rs 10
1
<?php
2
/**
3
 * @package midcom.baseclasses
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
use midcom\routing\loader;
10
use Symfony\Component\Routing\Router;
11
use midcom\routing\resolver;
12
use midcom\routing\plugin;
13
14
/**
15
 * Base class to encapsulate the component's routing, instantiated by the MidCOM
16
 * component interface.
17
 *
18
 * It provides an automatic mechanism for URL processing and validation, minimizing
19
 * the required work to get a new component running.
20
 *
21
 * <b>Request switch configuration</b>
22
 *
23
 * The class uses an array which aids in URL-to-function mapping. Handlers are distinguished
24
 * by the "URL-space" they handle. For each handler three functions are needed, one for the
25
 * request handle decision ("Can Handle Phase"), one for the
26
 * request handling ("Handle Phase") and one for output ("Output Phase"). These handlers refer to
27
 * another class which gets instantiated if necessary.
28
 *
29
 * All request handlers are contained in a single array, whose keys identify the various switch
30
 * configurations. These identifiers are only for informational purposes (they appear in the
31
 * debug log), so you could just resort to automatic array index numbering using the []
32
 * operator.
33
 *
34
 * Each request handler definition in the switch must contain these key/value pairs:
35
 *
36
 * - <b>mixed fixed_args:</b> This is either a string or an array and defines the fixed
37
 *   arguments that have to be present at the beginning of the URL to be handled. A
38
 *   string denotes a single argument, an array is used if more than one fixed argument
39
 *   is needed. If you do not have any fixed arguments, set this parameter to null, which
40
 *   is the default.
41
 * - <b>int variable_args:</b> Usually, there are a number of variables in the URL, like
42
 *   article IDs, or article names. This can be 0, indicating that no variable arguments are
43
 *   required, which is the default. For an unlimited number of variable_args set it to -1.
44
 *
45
 * - <b>mixed handler:</b> This is a definition of what method should be invoked to
46
 *   handle the request using the callable array syntax. The first array member must contain
47
 *   the name of an existing class.
48
 *   This value has no default and must be set. The actual methods called will have either an
49
 *   _handler_ or _show_ prefix.
50
 *
51
 * Example:
52
 *
53
 * <code>
54
 * $this->_request_switch[] = [
55
 *     'fixed_args' => ['registrations', 'view'],
56
 *     'variable_args' => 1,
57
 *     'handler' => ['net_nemein_registrations_regadmin', 'view']
58
 * ];
59
 * </code>
60
 *
61
 * This definition is usually located in either in the routes.yml file (preferred)
62
 * or the _on_initialize event handler.
63
 *
64
 * The handlers are processed in the order which they have been added to the array. This has
65
 * several implications:
66
 *
67
 * First, if you have two handlers with similar signatures, the latter might be hidden by the
68
 * former, for example the handler 'view' with two variable arguments includes the urls that
69
 * could match 'view', 'registration' with a single variable argument if processed in this order.
70
 * In these cases you have to add the most specific handlers first.
71
 *
72
 * Second, for performance reasons, you should try to add the handler which will be accessed
73
 * most of the time first (unless it conflicts with the first rule above), as this will speed
74
 * up average request processing.
75
 *
76
 * It is recommended that you add string-based identifiers to your handlers. This makes
77
 * debugging of URL parsing much easier, as MidCOM logs which request handlers are checked
78
 * in debug mode. The above example could use something like
79
 * `$this->_request_switch['registrations-view']` to do so. Just never prefix one of your
80
 * handlers with one underscores, this namespace is reserved for MidCOM usage.
81
 *
82
 * <b>Callback method signatures</b>
83
 *
84
 * <code>
85
 * /**
86
 *  * Exec handler example, with Docblock:
87
 *  * @param string $handler_id The ID of the handler.
88
 *  * @param array $args The argument list.
89
 *  * @param array $data The local request data.
90
 *  {@*}
91
 * public function _handler_xxx (string $handler_id, array $args, array &$data) {}
92
 *
93
 * /**
94
 *  * Show handler example, with Docblock:
95
 *  * @param string $handler_id The ID of the handler.
96
 *  * @param array $data The local request data.
97
 *  {@*}
98
 * public function _show_xxx (string $handler_id, array &$data) {}
99
 * </code>
100
 *
101
 * The two callbacks match the regular processing sequence of MidCOM.
102
 *
103
 * The main callback _handle_xxx is mandatory, _show_xxx is optional since the handle method can
104
 * return a response directly.
105
 *
106
 * As you can see, the system provides you with an easy way to keep track of the data
107
 * of your request, without having dozens of members for trivial flags. This data array
108
 * is automatically registered in the custom component context under the name
109
 * 'request_data', making it easily available within style elements as $data
110
 *
111
 * The data array can also be accessed by using the $_request_data member of this class,
112
 * which is the original data storage location for the request data.
113
 *
114
 * Note that the request data, for ease of use, already contains the L10n
115
 * Databases of the Component and MidCOM itself located in this class. They are stored
116
 * as 'l10n' and 'l10n_midcom'. Also available as 'config' is the current component
117
 * configuration and 'topic' will hold the current content topic.
118
 *
119
 * <b>Automatic handler class instantiation</b>
120
 *
121
 * If you specify a class name instead of a class instance as an exec handler, MidCOM will
122
 * automatically create an instance of that class type and initialize it. These
123
 * so-called handler classes must be a subclass of midcom_baseclasses_components_handler.
124
 *
125
 * The subclasses you create should look about this:
126
 *
127
 * <code>
128
 * class my_handler extends midcom_baseclasses_components_handler
129
 * {
130
 *     public function _on_initialize()
131
 *     {
132
 *         // Add class initialization code here, all members have been prepared
133
 *     }
134
 * }
135
 * </code>
136
 *
137
 * The two methods for each handler have the same signature as if they were in the
138
 * same class.
139
 *
140
 * @package midcom.baseclasses
141
 */
142
class midcom_baseclasses_components_viewer extends midcom_baseclasses_components_base
143
{
144
    /**
145
     * The topic for which we are handling a request.
146
     *
147
     * @var midcom_db_topic
148
     */
149
    public $_topic;
150
151
    /**
152
     * The current configuration.
153
     *
154
     * @var midcom_helper_configuration
155
     */
156
    public $_config;
157
158
    /**
159
     * Request specific data storage area. Registered in the component context
160
     * as ''.
161
     *
162
     * @var array
163
     */
164
    public $_request_data = [];
165
166
    /**
167
     * The node toolbar for the current request context. Not available during the can_handle
168
     * phase.
169
     *
170
     * @var midcom_helper_toolbar
171
     * @see midcom_services_toolbars
172
     */
173
    public $_node_toolbar;
174
175
    /**
176
     * The view toolbar for the current request context. Not available during the can_handle
177
     * phase.
178
     *
179
     * @var midcom_helper_toolbar
180
     * @see midcom_services_toolbars
181
     */
182
    public $_view_toolbar;
183
184
    /**
185
     * @var midcom_baseclasses_components_plugin
186
     */
187
    private $active_plugin;
188
189
    /**
190
     * @var Router
191
     */
192
    protected $router;
193
194
    /**
195
     * Request execution switch configuration.
196
     *
197
     * The main request switch data. You need to set this during construction,
198
     * it will be post-processed afterwards during initialize to provide a unified
199
     * set of data. Therefore you must not modify this switch after construction.
200
     *
201
     * @var array
202
     */
203
    protected $_request_switch = [];
204
205
    /**
206
     * The handler which has been declared to be able to handle the
207
     * request. The array will contain the original index of the handler in the
208
     * '_route' member for backtracking purposes. The variable argument list will be
209
     * placed into 'args' for performance reasons.
210
     *
211
     * @var Array
212
     */
213
    private $parameters;
214
215
    /**
216
     * Initializes the class, only basic variable assignment.
217
     *
218
     * Put all further initialization work into the _on_initialize event handler.
219
     */
220 341
    final public function __construct(midcom_db_topic $topic, midcom_helper_configuration $config, string $component)
221
    {
222 341
        $this->_topic = $topic;
223 341
        $this->_config = $config;
224 341
        $this->_component = $component;
225
226 341
        $this->_request_data['config'] = $this->_config;
227 341
        $this->_request_data['topic'] = null;
228 341
        $this->_request_data['l10n'] = $this->_l10n;
229 341
        $this->_request_data['l10n_midcom'] = $this->_l10n_midcom;
230
231 341
        $loader = new loader;
232 341
        $this->_request_switch = $loader->get_legacy_routes($component);
233
234 341
        $this->_on_initialize();
235 341
        $this->router = resolver::get_router($this->_component, $this->_request_switch);
236 341
    }
237
238 341
    public function get_router() : Router
239
    {
240 341
        return $this->router;
241
    }
242
243
    /**
244
     * Prepares the handler callback for execution.
245
     * This will create the handler class instance if required.
246
     */
247 338
    public function prepare_handler(array &$parameters)
248
    {
249 338
        $this->parameters =& $parameters;
250
251 338
        $parameters['handler'] = explode('::', $parameters['_controller'], 2);
252
253 338
        $this->initialize_handler(new $parameters['handler'][0]);
254 338
        midcom_core_context::get()->set_custom_key('request_data', $this->_request_data);
255 338
    }
256
257 338
    private function initialize_handler(midcom_baseclasses_components_handler $handler)
258
    {
259
        //For plugins, set the component name explicitly so that L10n and config can be found
260 338
        if (!empty($this->active_plugin)) {
261 84
            $handler->_component = $this->active_plugin->_component;
262
        }
263
264 338
        $handler->initialize($this, $this->router);
265 338
        $this->parameters['handler'][0] = $handler;
266 338
    }
267
268
    /**
269
     * Handle the request using the handler determined by the can_handle check.
270
     *
271
     * Before doing anything, it will call the _on_handle event handler to allow for
272
     * generic request preparation.
273
     *
274
     * @see _on_handle()
275
     */
276 338
    public function handle()
277
    {
278
        // Init
279 338
        $handler = $this->parameters['handler'][0];
280
281
        // Update the request data
282 338
        $this->_request_data['topic'] = $this->_topic;
283 338
        $this->_request_data['router'] = $this->router;
284
285
        // Get the toolbars for both the main request object and the handler object.
286 338
        $this->_node_toolbar = midcom::get()->toolbars->get_node_toolbar();
287 338
        $this->_view_toolbar = midcom::get()->toolbars->get_view_toolbar();
288 338
        $handler->_node_toolbar = $this->_node_toolbar;
289 338
        $handler->_view_toolbar = $this->_view_toolbar;
290
291
        // Add the handler ID to request data
292 338
        $this->_request_data['handler_id'] = $this->parameters['_route'];
293
294 338
        if (!array_key_exists('plugin_name', $this->_request_data)) {
295
            // We're not using a plugin handler, so call the general handle event handler
296 254
            $this->_on_handle($this->parameters['_route'], $this->parameters['args']);
297
        }
298 338
    }
299
300
    /**
301
     * Display the content, it uses the handler as determined by can_handle.
302
     */
303 97
    public function show()
304
    {
305 97
        if (!empty($this->parameters['handler'])) {
306 97
            $handler = $this->parameters['handler'][0];
307 97
            $method = "_show_{$this->parameters['handler'][1]}";
308
309 97
            $handler->$method($this->parameters['_route'], $this->_request_data);
310
        }
311 97
    }
312
313
    /**
314
     * Initialization event handler, called at the end of the initialization process
315
     *
316
     * Use this function instead of the constructor for all initialization work. You
317
     * can safely populate the request switch from here.
318
     *
319
     * You should not do anything else then general startup work, as this callback
320
     * executes <i>before</i> the can_handle phase. You don't know at this point
321
     * whether you are even able to handle the request. Thus, anything that is specific
322
     * to your request (like HTML HEAD tag adds) must not be done here. Use _on_handle
323
     * instead.
324
     */
325 222
    public function _on_initialize()
326
    {
327 222
    }
328
329
    /**
330
     * Component specific initialization code for the handle phase. The name of the request
331
     * handler is passed as an argument to the event handler.
332
     *
333
     * Note, that while you have the complete information around the request (handler id,
334
     * args and request data) available, it is strongly discouraged to handle everything
335
     * here. Instead, stay with the specific request handler methods as far as sensible.
336
     *
337
     * @param mixed $handler The ID (array key) of the handler that is responsible to handle
338
     *   the request.
339
     * @param array $args The argument list.
340
     */
341 6
    public function _on_handle($handler, array $args)
342
    {
343 6
    }
344
345
    /**
346
     * Create a new plugin namespace and map the configuration to it.
347
     * It allows flexible, user-configurable extension of components.
348
     *
349
     * Only very basic testing is done to keep runtime up, currently the system only
350
     * checks to prevent duplicate namespace registrations. In such a case,
351
     * midcom_error will be thrown. Any further validation won't be done before
352
     * can_handle determines that a plugin is actually in use.
353
     *
354
     * @param string $namespace The plugin namespace, checked against $args[0] during
355
     *     URL parsing.
356
     * @param array $config The configuration of the plugin namespace as outlined in
357
     *     the class introduction
358
     */
359 5
    public function register_plugin_namespace(string $namespace, array $config)
360
    {
361 5
        plugin::register_namespace($namespace, $config);
362 1
    }
363
364
    /**
365
     * Load the specified namespace/plugin combo.
366
     *
367
     * Any problem to load a plugin will be logged accordingly and false will be returned.
368
     * Critical errors will trigger midcom_error.
369
     *
370
     * @todo Allow for lazy plugin namespace configuration loading (using a callback)!
371
     *     This will make things more performant and integration with other components
372
     *     much easier.
373
     */
374 84
    public function load_plugin(string $name, midcom_baseclasses_components_plugin $plugin, array $config)
375
    {
376
        // Load the configuration into the request data, add the configured plugin name as
377
        // well so that URLs can be built.
378 84
        $this->_request_data['plugin_config'] = $config['config'] ?? null;
379 84
        $this->_request_data['plugin_name'] = $name;
380
381
        // Load remaining configuration, and prepare the plugin,
382
        // errors are logged by the callers.
383 84
        $this->router = resolver::get_router($plugin->_component);
384 84
        $plugin->initialize($this, $this->router);
385 84
        $this->active_plugin = $plugin;
386 84
    }
387
}
388