Passed
Push — master ( 20642a...d07293 )
by Andreas
19:33
created

loader::set_directories()   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 3
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 2
rs 10
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 midcom_connection;
15
use midgard_style;
16
use midgard_element;
17
use midcom_db_style;
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 $cache = [];
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
        $cache_key = $id . '::' . $name;
92
        if (array_key_exists($cache_key, $this->cache)) {
93
            return $this->cache[$cache_key];
94
        }
95
96
        $element_mc = midgard_element::new_collector('style', $id);
97
        $element_mc->set_key_property('guid');
98
        $element_mc->add_value_property('value');
99
        $element_mc->add_constraint('name', '=', $name);
100
        $element_mc->execute();
101
102
        if ($keys = $element_mc->list_keys()) {
103
            $element_guid = key($keys);
104
            midcom::get()->cache->content->register($element_guid);
105
            return $this->add_to_cache($cache_key, $element_mc->get_subkey($element_guid, 'value'));
0 ignored issues
show
Bug introduced by
It seems like $element_mc->get_subkey($element_guid, 'value') can also be of type false; however, parameter $content of midcom\templating\loader::add_to_cache() does only seem to accept string, 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

105
            return $this->add_to_cache($cache_key, /** @scrutinizer ignore-type */ $element_mc->get_subkey($element_guid, 'value'));
Loading history...
106
        }
107
108
        // No such element on this level, check parents
109
        $style_mc = midgard_style::new_collector('id', $id);
110
        $style_mc->set_key_property('guid');
111
        $style_mc->add_value_property('up');
112
        $style_mc->add_constraint('up', '>', 0);
113
        $style_mc->execute();
114
115
        if ($keys = $style_mc->list_keys()) {
116
            $style_guid = key($keys);
117
            midcom::get()->cache->content->register($style_guid);
118
            $up = $style_mc->get_subkey($style_guid, 'up');
119
            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

119
            return $this->get_element_in_styletree(/** @scrutinizer ignore-type */ $up, $name);
Loading history...
120
        }
121
122
        $this->cache[$cache_key] = null;
123
        return $this->cache[$cache_key];
124
    }
125
126
    /**
127
     * Try to get element from default style snippet
128
     */
129 202
    private function get_element_from_snippet(string $_element) : ?string
130
    {
131 202
        if (midcom::get()->config->get('theme')) {
132 202
            $src = "theme:{$_element}";
133 202
            if (array_key_exists($src, $this->cache)) {
134 18
                return $this->cache[$src];
135
            }
136 202
            if ($content = $this->get_element_from_theme($_element)) {
137 2
                return $this->add_to_cache($src, $content);
138
            }
139
        }
140
141 202
        foreach ($this->directories as $path) {
142 202
            $filename = $path . "/{$_element}.php";
143 202
            if (file_exists($filename)) {
144 192
                if (array_key_exists($filename, $this->cache)) {
145 76
                    return $this->cache[$filename];
146
                }
147 147
                return $this->add_to_cache($filename, file_get_contents($filename));
148
            }
149
        }
150 24
        return null;
151
    }
152
153
    /**
154
     * Get the content of the element by the passed element name.
155
     * Tries to resolve path according to theme-name & page
156
     */
157 202
    private function get_element_from_theme(string $element_name) : ?string
158
    {
159 202
        $theme = midcom::get()->config->get('theme');
160 202
        $path_array = explode('/', $theme);
161
162
        //get the page if there is one
163 202
        $page = midcom_connection::get_url('page_style');
164 202
        $substyle = midcom_core_context::get()->get_key(MIDCOM_CONTEXT_SUBSTYLE);
165
        //check if we have elements for the sub-styles
166 202
        while (!empty($path_array)) {
167 202
            $theme_path = implode('/', $path_array);
168 202
            $candidates = [];
169 202
            if ($substyle) {
170 31
                $candidates[] = '/' . $substyle . '/' . $element_name;
171
            }
172 202
            if ($page) {
173
                $candidates[] = $page . '/' . $element_name;
174
            }
175 202
            $candidates[] = '/' . $element_name;
176
177 202
            foreach ($candidates as $candidate) {
178 202
                $filename = OPENPSA2_THEME_ROOT . $theme_path . '/style' . $candidate . '.php';
179 202
                if (file_exists($filename)) {
180 2
                    return file_get_contents($filename);
181
                }
182
            }
183
184
            //remove last theme part
185 202
            array_pop($path_array);
186
        }
187
188 202
        return null;
189
    }
190
191 148
    private function add_to_cache(string $cache_key, string $content) : string
192
    {
193 148
        $this->cache[$cache_key] = $this->resolve_includes($content);
194 148
        return $this->cache[$cache_key];
195
    }
196
197 148
    private function resolve_includes(string $content) : string
198
    {
199
        return preg_replace_callback("/<\\(([a-zA-Z0-9 _-]+)\\)>/", function (array $matches) {
200 3
            $element = $matches[1];
201
202 3
            switch ($element) {
203 3
                case 'title':
204 2
                    return midcom::get()->config->get('midcom_site_title');
205 2
                case 'content':
206 2
                    return '<?php midcom_core_context::get()->show(); ?>';
207
                default:
208 1
                    if ($value = $this->get_element_from_theme($element)) {
209 1
                        return $this->resolve_includes($value);
210
                    }
211
                    return '';
212
            }
213 148
        }, $content);
214
    }
215
216
    /**
217
     * Initializes style sources from topic
218
     */
219 268
    public function initialize_from_topic(midcom_db_topic $topic, midcom_core_context $context)
220
    {
221 268
        $_st = 0;
222
        // get user defined style for component
223
        // style inheritance
224
        // should this be cached somehow?
225 268
        $style = $topic->style ?: $context->get_inherited_style();
226 268
        if (!$style) {
227 268
            $styleengine_default_styles = midcom::get()->config->get('styleengine_default_styles');
228 268
            if (isset($styleengine_default_styles[$topic->component])) {
229
                $style = $styleengine_default_styles[$topic->component];
230
            }
231
        }
232
233 268
        if ($style) {
234
            if ($_st = midcom_db_style::id_from_path($style)) {
235
                if ($substyle = $context->get_key(MIDCOM_CONTEXT_SUBSTYLE)) {
236
                    $chain = explode('/', $substyle);
237
                    foreach ($chain as $stylename) {
238
                        if ($_subst_id = midcom_db_style::id_from_path($stylename, $_st)) {
239
                            $_st = $_subst_id;
240
                        }
241
                    }
242
                }
243
            } elseif (substr($style, 0, 6) === 'theme:') {
244
                $theme_dir = OPENPSA2_THEME_ROOT . midcom::get()->config->get('theme') . '/style';
245
                $parts = explode('/', str_replace('theme:/', '', $style));
246
247
                foreach ($parts as &$part) {
248
                    $theme_dir .= '/' . $part;
249
                    $part = $theme_dir;
250
                }
251
                foreach (array_reverse(array_filter($parts, 'is_dir')) as $dirname) {
252
                    midcom::get()->style->prepend_styledir($dirname);
253
                }
254
            }
255
        }
256
257 268
        $context->set_custom_key(midcom_db_style::class, $_st);
258 268
    }
259
}
260