Passed
Push — master ( 5e945b...52d6d0 )
by Andreas
20:39
created

midcom_baseclasses_components_viewer::_on_handle()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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