Completed
Push — master ( b0bf1d...042f37 )
by Andreas
14:04
created

midcom_helper_head   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 591
Duplicated Lines 0 %

Test Coverage

Coverage 66.47%

Importance

Changes 2
Bugs 0 Features 2
Metric Value
eloc 167
dl 0
loc 591
ccs 119
cts 179
cp 0.6647
rs 5.04
c 2
b 0
f 2
wmc 57

21 Methods

Rating   Name   Duplication   Size   Complexity  
A add_link_head() 0 18 4
A add_jsfile() 0 12 3
A add_style_head() 0 3 1
A add_stylesheet() 0 11 2
A print_jsonload() 0 5 2
A print_head_elements() 0 7 2
A add_object_head() 0 3 1
A render_js() 0 8 2
A set_pagetitle() 0 3 1
A add_meta_head() 0 3 1
A add_jquery_state_script() 0 8 2
A add_jscript() 0 7 2
B enable_jquery() 0 45 6
A get_link_head() 0 3 1
B enable_jquery_ui() 0 42 9
A render() 0 33 6
A add_jsonload() 0 4 1
A inject_head_elements() 0 18 4
A render_jquery_statuses() 0 15 3
A _get_attribute_string() 0 10 3
A get_jshead_elements() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like midcom_helper_head often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use midcom_helper_head, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package midcom.helper
4
 * @author CONTENT CONTROL http://www.contentcontrol-berlin.de/
5
 * @copyright CONTENT CONTROL http://www.contentcontrol-berlin.de/
6
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License
7
 */
8
9
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
10
use Symfony\Component\HttpKernel\KernelEvents;
11
12
/**
13
 * Helper functions for managing HTML head
14
 *
15
 * @package midcom.helper
16
 */
17
class midcom_helper_head
18
{
19
    /**
20
     * Array with all JavaScript declarations for the page's head.
21
     *
22
     * @var array
23
     */
24
    private $_jshead = [];
25
26
    /**
27
     * Array with all JavaScript file inclusions.
28
     *
29
     * @var array
30
     */
31
    private $_jsfiles = [];
32
33
    /**
34
     * Array with all prepend JavaScript declarations for the page's head.
35
     *
36
     * @var array
37
     */
38
    private $_prepend_jshead = [];
39
40
    /**
41
     * Boolean showing if jQuery is enabled
42
     *
43
     * @var boolean
44
     */
45
    private $_jquery_enabled = false;
46
47
    private $_jquery_init_scripts = '';
48
49
    /**
50
     * Array with all JQuery state scripts for the page's head.
51
     *
52
     * @var array
53
     */
54
    private $_jquery_states = [];
55
56
    /**
57
     * Array with all linked URLs for HEAD.
58
     *
59
     * @var Array
60
     */
61
    private $_linkhrefs = [];
62
63
    /**
64
     * Array with all methods for the BODY's onload event.
65
     *
66
     * @var Array
67
     */
68
    private $_jsonload = [];
69
70
    /**
71
     * string with all metatags to go into the page head.
72
     *
73
     * @var string
74
     */
75
    private $_meta_head = '';
76
77
    /**
78
     * string with all object tags to go into a page's head.
79
     *
80
     * @var string
81
     */
82
    private $_object_head = '';
83
84
    /**
85
     * String with all css styles to go into a page's head.
86
     *
87
     * @var string
88
     */
89
    private $_style_head = '';
90
91
    /**
92
     * Array with all link elements to be included in a page's head.
93
     *
94
     * @var array
95
     */
96
    private $_link_head = [];
97
98
    const TOOLBAR_PLACEHOLDER = '<!-- MIDCOM_HEAD_ELEMENTS -->';
99
100
    private static $listener_added = false;
101
102
    /**
103
     * Sets the page title for the current context.
104
     *
105
     * This can be retrieved by accessing the component context key
106
     * MIDCOM_CONTEXT_PAGETITLE.
107
     *
108
     * @param string $string    The title to set.
109
     */
110 133
    public function set_pagetitle($string)
111
    {
112 133
        midcom_core_context::get()->set_key(MIDCOM_CONTEXT_PAGETITLE, $string);
113 133
    }
114
115
    /**
116
     * Register JavaScript File for referring in the page.
117
     *
118
     * This allows MidCOM components to register JavaScript code
119
     * during page processing. The site style code can then query this queued-up code
120
     * at anytime it likes. The queue-up SHOULD be done during the code-init phase,
121
     * while the print_head_elements output SHOULD be included in the HTML HEAD area and
122
     * the HTTP onload attribute returned by print_jsonload SHOULD be included in the
123
     * BODY-tag. Note, that these suggestions are not enforced, if you want a JScript
124
     * clean site, just omit the print calls and you should be fine in almost all
125
     * cases.
126
     *
127
     * The sequence of the add_jsfile and add_jscript commands is kept stable.
128
     *
129
     * @param string $url    The URL to the file to-be referenced.
130
     * @param boolean $prepend Whether to add the JS include to beginning of includes
131
     * @see add_jscript()
132
     * @see add_jsonload()
133
     * @see print_head_elements()
134
     * @see print_jsonload()
135
     */
136 341
    public function add_jsfile($url, $prepend = false)
137
    {
138
        // Adds a URL for a <script type="text/javascript" src="tinymce.js"></script>
139
        // like call. $url is inserted into src. Duplicates are omitted.
140 341
        if (!in_array($url, $this->_jsfiles)) {
141 70
            $this->_jsfiles[] = $url;
142 70
            $js_call = ['url' => $url];
143 70
            if ($prepend) {
144
                // Add the javascript include to the beginning, not the end of array
145
                array_unshift($this->_jshead, $js_call);
146
            } else {
147 70
                $this->_jshead[] = $js_call;
148
            }
149
        }
150 341
    }
151
152
    /**
153
     * Register JavaScript Code for output directly in the page.
154
     *
155
     * This allows components to register JavaScript code
156
     * during page processing. The site style can then query this queued-up code
157
     * at anytime it likes. The queue-up SHOULD be done during the code-init phase,
158
     * while the print_head_elements output SHOULD be included in the HTML HEAD area and
159
     * the HTTP onload attribute returned by print_jsonload SHOULD be included in the
160
     * BODY-tag. Note, that these suggestions are not enforced
161
     *
162
     * The sequence of the add_jsfile and add_jscript commands is kept stable.
163
     *
164
     * @param string $script    The code to be included directly in the page.
165
     * @see add_jsfile()
166
     * @see add_jsonload()
167
     * @see print_head_elements()
168
     * @see print_jsonload()
169
     */
170 28
    public function add_jscript($script, $defer = '', $prepend = false)
171
    {
172 28
        $js_call = ['content' => trim($script), 'defer' => $defer];
173 28
        if ($prepend) {
174
            $this->_prepend_jshead[] = $js_call;
175
        } else {
176 28
            $this->_jshead[] = $js_call;
177
        }
178 28
    }
179
180
    /**
181
     * Register JavaScript snippets to jQuery states.
182
     *
183
     * This allows components to register JavaScript code to the jQuery states.
184
     * Possible ready states: document.ready
185
     *
186
     * @param string $script    The code to be included in the state.
187
     * @param string $state    The state where to include the code to. Defaults to document.ready
188
     * @see print_jquery_statuses()
189
     */
190 12
    public function add_jquery_state_script($script, $state = 'document.ready')
191
    {
192 12
        $js_call = "\n" . trim($script) . "\n";
193
194 12
        if (!isset($this->_jquery_states[$state])) {
195 3
            $this->_jquery_states[$state] = $js_call;
196
        } else {
197 9
            $this->_jquery_states[$state] .= $js_call;
198
        }
199 12
    }
200
201
    /**
202
     * Register some object tags to be added to the head element.
203
     *
204
     * This allows components to register object tags to be placed in the
205
     * head section of the page.
206
     *
207
     * @param  string $script    The input between the <object></object> tags.
208
     * @param  array  $attributes Array of attribute=> value pairs to be placed in the tag.
209
     * @see print_head_elements()
210
     */
211
    public function add_object_head($script, $attributes = null)
212
    {
213
        $this->_object_head .= '<object' . $this->_get_attribute_string($attributes) . '>' . $script . "</object>\n";
214
    }
215
216
    /**
217
     *  Register a metatag to be added to the head element.
218
     *  This allows components to register metatags to be placed in the
219
     *  head section of the page.
220
     *
221
     *  @param  array  $attributes Array of attribute => value pairs to be placed in the tag.
222
     *  @see print_head_elements()
223
     */
224
    public function add_meta_head($attributes = null)
225
    {
226
        $this->_meta_head .= '<meta' . $this->_get_attribute_string($attributes) . ' />' . "\n";
227
    }
228
229
    /**
230
     * Register a styleblock / style link  to be added to the head element.
231
     * This allows components to register extra CSS sheets they wants to include.
232
     * in the head section of the page.
233
     *
234
     * @param  string $script    The input between the <style></style> tags.
235
     * @param  array  $attributes Array of attribute=> value pairs to be placed in the tag.
236
     * @see print_head_elements()
237
     */
238
    public function add_style_head($script, $attributes = null)
239
    {
240
        $this->_style_head .= '<style type="text/css"' . $this->_get_attribute_string($attributes) . '>' . $script . "</style>\n";
241
    }
242
243
    private function _get_attribute_string($attributes)
244
    {
245
        $string = '';
246
        if (null === $attributes) {
247
            return $string;
248
        }
249
        foreach ($attributes as $key => $val) {
250
            $string .= ' ' . $key . '="' . htmlspecialchars($val, ENT_COMPAT) . '"';
251
        }
252
        return $string;
253
    }
254
255
    /**
256
     * Register a link element to be placed in the page head.
257
     *
258
     * This allows components to register extra CSS links.
259
     * Example to use this to include a CSS link:
260
     * <code>
261
     * $attributes = array ('rel' => 'stylesheet',
262
     *                      'type' => 'text/css',
263
     *                      'href' => '/style.css'
264
     *                      );
265
     * midcom::get()->head->add_link_head($attributes);
266
     * </code>
267
     *
268
     * Each URL will only be added once. When trying to add the same URL a second time,
269
     * it will be moved to the end of the stack, so that CSS overrides behave as the developer
270
     * intended
271
     *
272
     * @param  array $attributes Array of attribute => value pairs to be placed in the tag.
273
     * @see print_head_elements()
274
     */
275 341
    public function add_link_head(array $attributes)
276
    {
277 341
        if (!array_key_exists('href', $attributes)) {
278
            return false;
279
        }
280
281
        // Register each URL only once
282 341
        if (($key = array_search($attributes['href'], $this->_linkhrefs)) !== false) {
283 341
            if (end($this->_linkhrefs) != $attributes['href']) {
284 341
                unset($this->_linkhrefs[$key]);
285 341
                $this->_linkhrefs[] = $attributes['href'];
286 341
                reset($this->_linkhrefs);
287
            }
288 341
            return false;
289
        }
290
291 61
        $this->_linkhrefs[] = $attributes['href'];
292 61
        $this->_link_head[$attributes['href']] = $attributes;
293 61
    }
294
295
    /**
296
     * Convenience shortcut for adding CSS files
297
     *
298
     * @param string $url The stylesheet URL
299
     * @param string $media The media type(s) for the stylesheet, if any
300
     */
301 341
    public function add_stylesheet($url, $media = false)
302
    {
303
        $attributes = [
304 341
            'rel'  => 'stylesheet',
305 341
            'type' => 'text/css',
306 341
            'href' => $url,
307
        ];
308 341
        if ($media) {
309 23
            $attributes['media'] = $media;
310
        }
311 341
        $this->add_link_head($attributes);
312 341
    }
313
314
    /**
315
     * Register a JavaScript method for the body onload event
316
     *
317
     * This allows components to register JavaScript code
318
     * during page processing. The site style can then query this queued-up code
319
     * at anytime it likes. The queue-up SHOULD be done during the code-init phase,
320
     * while the print_head_elements output SHOULD be included in the HTML HEAD area and
321
     * the HTTP onload attribute returned by print_jsonload SHOULD be included in the
322
     * BODY-tag. Note that these suggestions are not enforced.
323
     *
324
     * @param string $method    The name of the method to be called on page startup, including parameters but excluding the ';'.
325
     * @see add_jsfile()
326
     * @see add_jscript()
327
     * @see print_head_elements()
328
     * @see print_jsonload()
329
     */
330
    public function add_jsonload($method)
331
    {
332
        // Adds a method name for <body onload=".."> The string must not end with a ;, it is added automagically
333
        $this->_jsonload[] = $method;
334
    }
335
336
    /**
337
     * Echo the registered javascript code.
338
     *
339
     * This allows components to register JavaScript code
340
     * during page processing. The site style code can then query this queued-up code
341
     * at anytime it likes. The queue-up SHOULD be done during the code-init phase,
342
     * while the print_head_elements output SHOULD be included in the HTML HEAD area and
343
     * the HTTP onload attribute returned by print_jsonload SHOULD be included in the
344
     * BODY-tag. Note, that these suggestions are not enforced
345
     *
346
     * The sequence of the add_jsfile and add_jscript commands is kept stable.
347
     *
348
     * This is usually called during the BODY region of your style:
349
     *
350
     * <code>
351
     * <html>
352
     *     <body <?php midcom::get()->head->print_jsonload();?>>
353
     *            <!-- your actual body -->
354
     *     </body>
355
     * </html>
356
     * </code>
357
     *
358
     * @see add_jsfile()
359
     * @see add_jscript()
360
     * @see add_jsonload()
361
     * @see print_head_elements()
362
     */
363 26
    public function print_jsonload()
364
    {
365 26
        if (!empty($this->_jsonload)) {
366
            $calls = implode("; ", $this->_jsonload);
367
            echo " onload=\"$calls\" ";
368
        }
369 26
    }
370
371
    /**
372
     * Marks where the _head elements added should be rendered.
373
     *
374
     * Place the method within the <head> section of your page.
375
     *
376
     * This allows components to register HEAD elements
377
     * during page processing. The site style can then query this queued-up code
378
     * at anytime it likes. The queue-up SHOULD be done during the code-init phase,
379
     * while the print_head_elements output SHOULD be included in the HTML HEAD area and
380
     * the HTTP onload attribute returned by print_jsonload SHOULD be included in the
381
     * BODY tag. Note that these suggestions are not enforced
382
     *
383
     * @see add_link_head()
384
     * @see add_object_head()
385
     * @see add_style_head()
386
     * @see add_meta_head()
387
     * @see add_jsfile()
388
     * @see add_jscript()
389
     */
390 29
    public function print_head_elements()
391
    {
392 29
        if (!self::$listener_added) {
393 1
            midcom::get()->dispatcher->addListener(KernelEvents::RESPONSE, [$this, 'inject_head_elements']);
394 1
            self::$listener_added = true;
395
        }
396 29
        echo self::TOOLBAR_PLACEHOLDER;
397 29
    }
398
399
    /**
400
     * This function renders the elements added by the various add methods
401
     * and injects them into the response
402
     *
403
     * @param FilterResponseEvent $event
404
     */
405 335
    public function inject_head_elements(FilterResponseEvent $event)
406
    {
407 335
        if (!$event->isMasterRequest()) {
408 335
            return;
409
        }
410
        $response = $event->getResponse();
411
        $content = $response->getContent();
412
413
        if (strpos($content, self::TOOLBAR_PLACEHOLDER) === false) {
414
            return;
415
        }
416
417
        $head = $this->render();
418
        $new_content = str_replace(self::TOOLBAR_PLACEHOLDER, $head, $content);
419
        $response->setContent($new_content);
420
        if ($length = $response->headers->get('Content-Length')) {
421
            $delta = strlen($head) - strlen(self::TOOLBAR_PLACEHOLDER);
422
            $response->headers->set('Content-Length', $length + $delta);
423
        }
424
    }
425
426 11
    public function render() : string
427
    {
428 11
        $head = $this->_meta_head;
429 11
        foreach ($this->_linkhrefs as $url) {
430
            $attributes = $this->_link_head[$url];
431
            $is_conditional = false;
432
433
            if (array_key_exists('condition', $attributes)) {
434
                $head .= "<!--[if {$attributes['condition']}]>\n";
435
                $is_conditional = true;
436
                unset($attributes['condition']);
437
            }
438
439
            $head .= "<link" . $this->_get_attribute_string($attributes) . " />\n";
440
441
            if ($is_conditional) {
442
                $head .= "<![endif]-->\n";
443
            }
444
        }
445
446 11
        $head .= $this->_object_head;
447 11
        $head .= $this->_style_head;
448
449 11
        if ($this->_jquery_enabled) {
450 11
            $head .= $this->_jquery_init_scripts;
451
        }
452
453 11
        if (!empty($this->_prepend_jshead)) {
454
            $head .= array_reduce($this->_prepend_jshead, [$this, 'render_js'], '');
455
        }
456
457 11
        $head .= array_reduce($this->_jshead, [$this, 'render_js'], '');
458 11
        return $head . $this->render_jquery_statuses();
459
    }
460
461 11
    private function render_js(string $carry, array $js_call) : string
462
    {
463 11
        if (array_key_exists('url', $js_call)) {
464 11
            return $carry . '<script type="text/javascript" src="' . $js_call['url'] . "\"></script>\n";
465
        }
466 11
        $carry .= '<script type="text/javascript"' . $js_call['defer'] . ">\n";
467 11
        $carry .= $js_call['content'] . "\n";
468 11
        return $carry . "</script>\n";
469
    }
470
471 5
    public function get_jshead_elements()
472
    {
473 5
        return $this->_prepend_jshead + $this->_jshead;
474
    }
475
476
    public function get_link_head()
477
    {
478
        return $this->_link_head;
479
    }
480
481
    /**
482
     * Init jQuery
483
     *
484
     * This method adds jQuery support to the page
485
     */
486 341
    public function enable_jquery($version = null)
487
    {
488 341
        if ($this->_jquery_enabled) {
489 341
            return;
490
        }
491
492 12
        if (!$version) {
493 12
            $version = midcom::get()->config->get('jquery_version');
494
        }
495
496 12
        $this->_jquery_init_scripts .= "\n";
497
498 12
        if (midcom::get()->config->get('jquery_load_from_google')) {
499
            // Use Google's hosted jQuery version
500
            $this->_jquery_init_scripts .= "<script src=\"http://www.google.com/jsapi\"></script>\n";
501
            $this->_jquery_init_scripts .= "<script>\n";
502
            $this->_jquery_init_scripts .= "    google.load('jquery', '{$version}');\n";
503
            $this->_jquery_init_scripts .= "</script>\n";
504
        } else {
505 12
            $url = MIDCOM_STATIC_URL . "/jQuery/jquery-{$version}.js";
506 12
            if (midcom::get()->config->get('jquery_version_oldie')) {
507 12
                $oldie_url = MIDCOM_STATIC_URL . '/jQuery/jquery-' . midcom::get()->config->get('jquery_version_oldie') . '.js';
508 12
                $this->_jquery_init_scripts .= "<!--[if lt IE 9]>\n";
509 12
                $this->_jquery_init_scripts .= "<script type=\"text/javascript\" src=\"{$oldie_url}\"></script>\n";
510 12
                $this->_jquery_init_scripts .= "<![endif]-->\n";
511 12
                $this->_jquery_init_scripts .= "<!--[if gte IE 9]><!-->\n";
512 12
                $this->_jquery_init_scripts .= "<script type=\"text/javascript\" src=\"{$url}\"></script>\n";
513 12
                $this->_jquery_init_scripts .= "<!--<![endif]-->\n";
514
            } else {
515
                $this->_jquery_init_scripts .= "<script type=\"text/javascript\" src=\"{$url}\"></script>\n";
516
            }
517
        }
518
519 12
        if (!defined('MIDCOM_JQUERY_UI_URL')) {
520 1
            define('MIDCOM_JQUERY_UI_URL', MIDCOM_STATIC_URL . "/jQuery/jquery-ui-" . midcom::get()->config->get('jquery_ui_version'));
521
        }
522
523 12
        $script  = "var MIDCOM_STATIC_URL = '" . MIDCOM_STATIC_URL . "';\n";
524 12
        $script .= "var MIDCOM_PAGE_PREFIX = '" . midcom_connection::get_url('self') . "';\n";
525
526 12
        $this->_jquery_init_scripts .= "<script type=\"text/javascript\">\n";
527 12
        $this->_jquery_init_scripts .= trim($script) . "\n";
528 12
        $this->_jquery_init_scripts .= "</script>\n";
529
530 12
        $this->_jquery_enabled = true;
531 12
    }
532
533
    /**
534
     * Renders the scripts added by the add_jquery_state_script method.
535
     *
536
     * This method is called from print_head_elements method.
537
     *
538
     * @see add_jquery_state_script()
539
     * @see print_head_elements()
540
     */
541 11
    private function render_jquery_statuses() : string
542
    {
543 11
        if (empty($this->_jquery_states)) {
544 11
            return '';
545
        }
546
547
        $content = '';
548
        foreach ($this->_jquery_states as $status => $scripts) {
549
            list($status_target, $status_method) = explode('.', $status);
550
            $content .= "jQuery({$status_target}).{$status_method}(function() {\n";
551
            $content .= $scripts . "\n";
552
            $content .= "});\n";
553
        }
554
555
        return $this->render_js('', ['content' => $content, 'defer' => '']);
556
    }
557
558
    /**
559
     * Add jquery ui components
560
     *
561
     * core and widget are loaded automatically. Also loads jquery.ui theme,
562
     * either the configured theme one or a hardcoded default (base theme)
563
     *
564
     * @param array $components The components that should be loaded
565
     */
566 341
    public function enable_jquery_ui(array $components = [])
567
    {
568 341
        $this->enable_jquery();
569 341
        $this->add_jsfile(MIDCOM_JQUERY_UI_URL . '/core.min.js');
570
571 341
        foreach ($components as $component) {
572 341
            $path = $component;
573 341
            if (strpos($component, 'effect') === 0) {
574 2
                if ($component !== 'effect') {
575 2
                    $path = 'effects/' . $component;
576
                }
577
            } else {
578 341
                $path = 'widgets/' . $component;
579
            }
580
581 341
            $this->add_jsfile(MIDCOM_JQUERY_UI_URL . '/' . $path . '.min.js');
582
583 341
            if ($component == 'datepicker') {
584 45
                $lang = midcom::get()->i18n->get_current_language();
585
                /*
586
                 * The calendar doesn't have all lang files and some are named differently
587
                 * Since a missing lang file causes the calendar to break, let's make extra sure
588
                 * that this won't happen
589
                 */
590 45
                if (!file_exists(MIDCOM_STATIC_ROOT . "/jQuery/jquery-ui-" . midcom::get()->config->get('jquery_ui_version') . "/i18n/datepicker-{$lang}.min.js")) {
591 45
                    $lang = midcom::get()->i18n->get_fallback_language();
592 45
                    if (!file_exists(MIDCOM_STATIC_ROOT . "/jQuery/jquery-ui-" . midcom::get()->config->get('jquery_ui_version') . "/i18n/datepicker-{$lang}.min.js")) {
593 45
                        $lang = null;
594
                    }
595
                }
596
597 45
                if ($lang) {
598 341
                    $this->add_jsfile(MIDCOM_JQUERY_UI_URL . "/i18n/datepicker-{$lang}.min.js");
599
                }
600
            }
601
        }
602
603 341
        $this->add_stylesheet(MIDCOM_STATIC_URL . '/jQuery/jquery-ui-1.12.icon-font.min.css');
604 341
        if (midcom::get()->config->get('jquery_ui_theme')) {
605
            $this->add_stylesheet(midcom::get()->config->get('jquery_ui_theme'));
606
        } else {
607 341
            $this->add_stylesheet(MIDCOM_JQUERY_UI_URL . '/themes/base/jquery-ui.min.css');
608
        }
609 341
    }
610
}
611