Completed
Push — master ( 80f9cb...a11ce5 )
by Andreas
29:10 queued 09:54
created

midcom_baseclasses_components_viewer::get_router()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
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.inc 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 mixed $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 ($handler_id, array $args, array &$data) {}
92
 *
93
 * /**
94
 *  * Show handler example, with Docblock:
95
 *  * @param mixed $handler_id The ID of the handler.
96
 *  * @param array $data The local request data.
97
 *  {@*}
98
 * public function _show_xxx ($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 $_handler;
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 &$request)
248
    {
249 338
        $this->_handler =& $request;
250
251 338
        if (!str_contains($request['_controller'], '::')) {
252
            // Support for handlers in request class (deprecated)
253
            $request['handler'] = [&$this, $request['handler']];
254
            return;
255
        }
256 338
        $request['handler'] = explode('::', $request['_controller'], 2);
257
258 338
        $this->initialize_handler(new $request['handler'][0]);
259 338
        midcom_core_context::get()->set_custom_key('request_data', $this->_request_data);
260 338
    }
261
262 338
    private function initialize_handler(midcom_baseclasses_components_handler $handler)
263
    {
264
        //For plugins, set the component name explicitly so that L10n and config can be found
265 338
        if (!empty($this->active_plugin)) {
266 84
            $handler->_component = $this->active_plugin->_component;
267
        }
268
269 338
        $handler->initialize($this, $this->router);
270 338
        $this->_handler['handler'][0] = $handler;
271 338
    }
272
273
    /**
274
     * Handle the request using the handler determined by the can_handle check.
275
     *
276
     * Before doing anything, it will call the _on_handle event handler to allow for
277
     * generic request preparation.
278
     *
279
     * @see _on_handle()
280
     */
281 338
    public function handle()
282
    {
283
        // Init
284 338
        $handler = $this->_handler['handler'][0];
285
286
        // Update the request data
287 338
        $this->_request_data['topic'] = $this->_topic;
288 338
        $this->_request_data['router'] = $this->router;
289
290
        // Get the toolbars for both the main request object and the handler object.
291 338
        $this->_node_toolbar = midcom::get()->toolbars->get_node_toolbar();
292 338
        $this->_view_toolbar = midcom::get()->toolbars->get_view_toolbar();
293 338
        $handler->_node_toolbar = $this->_node_toolbar;
294 338
        $handler->_view_toolbar = $this->_view_toolbar;
295
296
        // Add the handler ID to request data
297 338
        $this->_request_data['handler_id'] = $this->_handler['_route'];
298
299 338
        if (!array_key_exists('plugin_name', $this->_request_data)) {
300
            // We're not using a plugin handler, so call the general handle event handler
301 254
            $this->_on_handle($this->_handler['_route'], $this->_handler['args']);
302
        }
303 338
    }
304
305
    /**
306
     * Display the content, it uses the handler as determined by can_handle.
307
     */
308 97
    public function show()
309
    {
310 97
        if (empty($this->_handler['handler'])) {
311
            return;
312
        }
313
314
        // Call the handler:
315 97
        $handler = $this->_handler['handler'][0];
316 97
        $method = "_show_{$this->_handler['handler'][1]}";
317
318 97
        $handler->$method($this->_handler['_route'], $this->_request_data);
319 97
    }
320
321
    /**
322
     * Initialization event handler, called at the end of the initialization process
323
     *
324
     * Use this function instead of the constructor for all initialization work. You
325
     * can safely populate the request switch from here.
326
     *
327
     * You should not do anything else then general startup work, as this callback
328
     * executes <i>before</i> the can_handle phase. You don't know at this point
329
     * whether you are even able to handle the request. Thus, anything that is specific
330
     * to your request (like HTML HEAD tag adds) must not be done here. Use _on_handle
331
     * instead.
332
     */
333 222
    public function _on_initialize()
334
    {
335 222
    }
336
337
    /**
338
     * Component specific initialization code for the handle phase. The name of the request
339
     * handler is passed as an argument to the event handler.
340
     *
341
     * Note, that while you have the complete information around the request (handler id,
342
     * args and request data) available, it is strongly discouraged to handle everything
343
     * here. Instead, stay with the specific request handler methods as far as sensible.
344
     *
345
     * @param mixed $handler The ID (array key) of the handler that is responsible to handle
346
     *   the request.
347
     * @param array $args The argument list.
348
     */
349 6
    public function _on_handle($handler, array $args)
350
    {
351 6
    }
352
353
    /**
354
     * Create a new plugin namespace and map the configuration to it.
355
     * It allows flexible, user-configurable extension of components.
356
     *
357
     * Only very basic testing is done to keep runtime up, currently the system only
358
     * checks to prevent duplicate namespace registrations. In such a case,
359
     * midcom_error will be thrown. Any further validation won't be done before
360
     * can_handle determines that a plugin is actually in use.
361
     *
362
     * @param string $namespace The plugin namespace, checked against $args[0] during
363
     *     URL parsing.
364
     * @param array $config The configuration of the plugin namespace as outlined in
365
     *     the class introduction
366
     */
367 5
    public function register_plugin_namespace(string $namespace, array $config)
368
    {
369 5
        plugin::register_namespace($namespace, $config);
370 1
    }
371
372
    /**
373
     * Load the specified namespace/plugin combo.
374
     *
375
     * Any problem to load a plugin will be logged accordingly and false will be returned.
376
     * Critical errors will trigger midcom_error.
377
     *
378
     * @todo Allow for lazy plugin namespace configuration loading (using a callback)!
379
     *     This will make things more performant and integration with other components
380
     *     much easier.
381
     */
382 84
    public function load_plugin(string $name, midcom_baseclasses_components_plugin $plugin, array $config)
383
    {
384
        // Load the configuration into the request data, add the configured plugin name as
385
        // well so that URLs can be built.
386 84
        $this->_request_data['plugin_config'] = $config['config'] ?? null;
387 84
        $this->_request_data['plugin_name'] = $name;
388
389
        // Load remaining configuration, and prepare the plugin,
390
        // errors are logged by the callers.
391 84
        $this->router = resolver::get_router($plugin->_component);
392 84
        $plugin->initialize($this, $this->router);
393 84
        $this->active_plugin = $plugin;
394 84
    }
395
}
396