Passed
Push — master ( 1fec05...4cfbdd )
by Andreas
16:49
created

loader::get_element_from_snippet()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 7
eloc 14
c 2
b 0
f 0
nc 10
nop 1
dl 0
loc 23
ccs 15
cts 15
cp 1
crap 7
rs 8.8333
1
<?php
2
/**
3
 * @package midcom.templating
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
namespace midcom\templating;
10
11
use midcom;
12
use midcom_db_topic;
13
use midcom_core_context;
14
use midgard_style;
15
use midgard_element;
16
use midcom_db_style;
17
use midcom_helper_misc;
18
19
/**
20
 * templating loader class
21
 *
22
 * Style Inheritance
23
 *
24
 * The basic path the styleloader follows to find a style element is:
25
 * 1. Topic style -> if the current topic has a style set
26
 * 2. Inherited topic style -> if the topic inherits a style from another topic.
27
 * 3. Site-wide per-component default style -> if defined in MidCOM configuration key styleengine_default_styles
28
 * 4. Theme style -> the style of the MidCOM component.
29
 * 5. The file style. This is usually the elements found in the component's style directory.
30
 *
31
 * Regarding no. 5:
32
 * It is possible to add extra file styles if so is needed for example by a portal component.
33
 * This is done either using the append/prepend component_style functions of midcom::get()->style or by setting it
34
 * to another directory by calling (append|prepend)_styledir directly.
35
 *
36
 * @package midcom.templating
37
 */
38
class loader
39
{
40
    /**
41
     * Default style element cache
42
     *
43
     * @var string[]
44
     */
45
    private $_snippets = [];
46
47
    /**
48
     * The stack of directories to check for styles.
49
     *
50
     * @var string[]
51
     */
52
    private $directories = [];
53
54 268
    public function set_directories(?midcom_db_topic $topic, array $prepend, array $append)
55
    {
56 268
        $this->directories = $prepend;
57 268
        if ($snippetdir = $this->get_component_snippetdir($topic)) {
58 268
            $this->directories[] = $snippetdir;
59
        }
60 268
        $this->directories = array_merge($this->directories, $append);
61 268
    }
62
63
    /**
64
     * Gets the component styledir associated with the topic's component.
65
     *
66
     * @return mixed the path to the component's style directory.
67
     */
68 268
    private function get_component_snippetdir(?midcom_db_topic $topic) : ?string
69
    {
70 268
        if (empty($topic->component)) {
71 2
            return null;
72
        }
73 268
        return midcom::get()->componentloader->path_to_snippetpath($topic->component) . "/style";
74
    }
75
76 202
    public function get_element(string $name, ?int $scope = null) : ?string
77
    {
78 202
        if ($scope && $content = $this->get_element_in_styletree($scope, $name)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $scope of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
79
            return $content;
80
        }
81 202
        return $this->get_element_from_snippet($name);
82
    }
83
84
    /**
85
     * Returns a style element that matches $name and is in style $id.
86
     * It also returns an element if it is not in the given style,
87
     * but in one of its parent styles.
88
     */
89
    private function get_element_in_styletree(int $id, string $name) : ?string
90
    {
91
        static $cached = [];
92
        $cache_key = $id . '::' . $name;
93
94
        if (array_key_exists($cache_key, $cached)) {
95
            return $cached[$cache_key];
96
        }
97
98
        $element_mc = midgard_element::new_collector('style', $id);
99
        $element_mc->set_key_property('guid');
100
        $element_mc->add_value_property('value');
101
        $element_mc->add_constraint('name', '=', $name);
102
        $element_mc->execute();
103
104
        if ($keys = $element_mc->list_keys()) {
105
            $element_guid = key($keys);
106
            $cached[$cache_key] = $element_mc->get_subkey($element_guid, 'value');
107
            midcom::get()->cache->content->register($element_guid);
108
            return $cached[$cache_key];
109
        }
110
111
        // No such element on this level, check parents
112
        $style_mc = midgard_style::new_collector('id', $id);
113
        $style_mc->set_key_property('guid');
114
        $style_mc->add_value_property('up');
115
        $style_mc->add_constraint('up', '>', 0);
116
        $style_mc->execute();
117
118
        if ($keys = $style_mc->list_keys()) {
119
            $style_guid = key($keys);
120
            midcom::get()->cache->content->register($style_guid);
121
            $up = $style_mc->get_subkey($style_guid, 'up');
122
            return $this->get_element_in_styletree($up, $name);
0 ignored issues
show
Bug introduced by
It seems like $up can also be of type false; however, parameter $id of midcom\templating\loader..._element_in_styletree() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

122
            return $this->get_element_in_styletree(/** @scrutinizer ignore-type */ $up, $name);
Loading history...
123
        }
124
125
        $cached[$cache_key] = null;
126
        return $cached[$cache_key];
127
    }
128
129
    /**
130
     * Try to get element from default style snippet
131
     */
132 202
    private function get_element_from_snippet(string $_element) : ?string
133
    {
134 202
        if (midcom::get()->config->get('theme')) {
135 202
            $src = "theme:{$_element}";
136 202
            if (array_key_exists($src, $this->_snippets)) {
137 18
                return $this->_snippets[$src];
138
            }
139 202
            if ($content = midcom_helper_misc::get_element_content($_element)) {
140 2
                $this->_snippets[$src] = $content;
141 2
                return $this->_snippets[$src];
142
            }
143
        }
144
145 202
        foreach ($this->directories as $path) {
146 202
            $filename = $path . "/{$_element}.php";
147 202
            if (file_exists($filename)) {
148 192
                if (!array_key_exists($filename, $this->_snippets)) {
149 147
                    $this->_snippets[$filename] = file_get_contents($filename);
150
                }
151 192
                return $this->_snippets[$filename];
152
            }
153
        }
154 24
        return null;
155
    }
156
157
    /**
158
     * Initializes style sources from topic
159
     */
160 268
    public function initialize_from_topic(midcom_db_topic $topic, midcom_core_context $context)
161
    {
162 268
        $_st = 0;
163
        // get user defined style for component
164
        // style inheritance
165
        // should this be cached somehow?
166 268
        if ($style = $topic->style ?: $context->get_inherited_style()) {
167
            if (substr($style, 0, 6) === 'theme:') {
168
                $theme_dir = OPENPSA2_THEME_ROOT . midcom::get()->config->get('theme') . '/style';
169
                $parts = explode('/', str_replace('theme:/', '', $style));
170
171
                foreach ($parts as &$part) {
172
                    $theme_dir .= '/' . $part;
173
                    $part = $theme_dir;
174
                }
175
                foreach (array_reverse(array_filter($parts, 'is_dir')) as $dirname) {
176
                    midcom::get()->style->prepend_styledir($dirname);
177
                }
178
            } else {
179
                $_st = midcom_db_style::id_from_path($style);
180
            }
181
        } else {
182
            // Get style from sitewide per-component defaults.
183 268
            $styleengine_default_styles = midcom::get()->config->get('styleengine_default_styles');
184 268
            if (isset($styleengine_default_styles[$topic->component])) {
185
                $_st = midcom_db_style::id_from_path($styleengine_default_styles[$topic->component]);
186
            }
187
        }
188
189 268
        if ($_st) {
190
            $substyle = $context->get_key(MIDCOM_CONTEXT_SUBSTYLE);
191
192
            if (is_string($substyle)) {
193
                $chain = explode('/', $substyle);
194
                foreach ($chain as $stylename) {
195
                    if ($_subst_id = midcom_db_style::id_from_path($stylename, $_st)) {
196
                        $_st = $_subst_id;
197
                    }
198
                }
199
            }
200
        }
201 268
        $context->set_custom_key(midcom_db_style::class, $_st);
202 268
    }
203
}
204