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

midcom_helper_style::load()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0932

Importance

Changes 0
Metric Value
cc 2
eloc 6
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 11
ccs 5
cts 7
cp 0.7143
crap 2.0932
rs 10
1
<?php
2
/**
3
 * @package midcom.helper
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\templating\loader;
10
11
/**
12
 * This class is responsible for all style management. It is
13
 * accessible through the midcom::get()->style object.
14
 *
15
 * The method <code>show($style)</code> returns the style element $style for the current
16
 * component:
17
 *
18
 * It checks whether a style path is defined for the current component.
19
 *
20
 * - If there is a user defined style path, the element named $style in
21
 *   this path is returned,
22
 * - otherwise the element "$style" is taken from the default style of the
23
 *   current component (/path/to/component/style/$path).
24
 *
25
 * (The default fallback is always the default style, e.g. if $style
26
 * is not in the user defined style path)
27
 *
28
 * To enable cross-style referencing and provide the opportunity to access
29
 * any style element, "show" can be called with a full qualified style
30
 * path (like "/mystyle/element1", while the current page's style may be set
31
 * to "/yourstyle").
32
 *
33
 * Note: To make sure sub-styles and elements included in styles are handled
34
 * correctly, use:
35
 *
36
 * <code>
37
 * <?php midcom_show_style ("elementname"); ?>
38
 * </code>
39
 *
40
 * @package midcom.helper
41
 */
42
class midcom_helper_style
43
{
44
    /**
45
     * Current topic
46
     *
47
     * @var midcom_db_topic
48
     */
49
    private $_topic;
50
51
    /**
52
     * Context stack
53
     *
54
     * @var midcom_core_context[]
55
     */
56
    private $_context = [];
57
58
    /**
59
     * List of styledirs to handle after componentstyle
60
     *
61
     * @var array
62
     */
63
    private $_styledirs_append = [];
64
65
    /**
66
     * List of styledirs to handle before componentstyle
67
     *
68
     * @var array
69
     */
70
    private $_styledirs_prepend = [];
71
72
    /**
73
     * @var loader
74
     */
75
    private $loader;
76
77
    /**
78
     * Data to pass to the style
79
     *
80
     * @var array
81
     */
82
    public $data;
83
84 1
    public function __construct()
85
    {
86 1
        $this->loader = new loader;
87 1
    }
88
89
    /**
90
     * Looks for a style element matching $path (either in a user defined style
91
     * or the default style snippetdir) and displays/evaluates it.
92
     *
93
     * @param string $path    The style element to show.
94
     * @return boolean            True on success, false otherwise.
95
     */
96 202
    public function show($path) : bool
97
    {
98 202
        if ($this->_context === []) {
99
            debug_add("Trying to show '{$path}' but there is no context set", MIDCOM_LOG_INFO);
100
            return false;
101
        }
102
103 202
        $style = $this->load($path);
104
105 202
        if ($style === null) {
106 24
            if ($path == 'ROOT') {
107
                // Go to fallback ROOT instead of displaying a blank page
108
                return $this->show_midcom($path);
109
            }
110
111 24
            debug_add("The element '{$path}' could not be found.", MIDCOM_LOG_INFO);
112 24
            return false;
113
        }
114 201
        $this->render($style, $path);
115
116 201
        return true;
117
    }
118
119
    /**
120
     * Load style element content
121
     *
122
     * @param string $path The element name
123
     */
124 202
    private function load($path) : ?string
125
    {
126 202
        $element = $path;
127
        // we have full qualified path to element
128 202
        if (preg_match("|(.*)/(.*)|", $path, $matches)) {
129
            $styleid = midcom_db_style::id_from_path($matches[1]);
130
            $element = $matches[2];
131
        }
132 202
        $scope = $styleid ?? $this->_context[0]->get_custom_key(midcom_db_style::class);
133
134 202
        return $this->loader->get_element($element, $scope);
0 ignored issues
show
Bug introduced by
It seems like $scope can also be of type false; however, parameter $scope of midcom\templating\loader::get_element() does only seem to accept integer|null, 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

134
        return $this->loader->get_element($element, /** @scrutinizer ignore-type */ $scope);
Loading history...
135
    }
136
137
    /**
138
     * Renders the style element with current request data
139
     *
140
     * @param string $style The style element content
141
     * @param string $path the element's name
142
     * @throws midcom_error
143
     */
144 201
    private function render(string $style, string $path)
145
    {
146 201
        if (midcom::get()->config->get('wrap_style_show_with_name')) {
147
            $style = "\n<!-- Start of style '{$path}' -->\n" . $style;
148
            $style .= "\n<!-- End of style '{$path}' -->\n";
149
        }
150
151
        // This is a bit of a hack to allow &(); tags
152 201
        $preparsed = midcom_helper_misc::preparse($style);
153 201
        if (midcom_core_context::get()->has_custom_key('request_data')) {
154 201
            $data =& midcom_core_context::get()->get_custom_key('request_data');
0 ignored issues
show
Unused Code introduced by
The assignment to $data is dead and can be removed.
Loading history...
155
        }
156
157 201
        if (eval('?>' . $preparsed) === false) {
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
158
            // Note that src detection will be semi-reliable, as it depends on all errors being
159
            // found before caching kicks in.
160
            throw new midcom_error("Failed to parse style element '{$path}', see above for PHP errors.");
161
        }
162 201
    }
163
164
    /**
165
     * Looks for a midcom core style element matching $path and displays/evaluates it.
166
     * This offers a bit reduced functionality and will only look in the DB root style,
167
     * the theme directory and midcom's style directory, because it has to work even when
168
     * midcom is not yet fully initialized
169
     *
170
     * @param string $path    The style element to show.
171
     * @return boolean            True on success, false otherwise.
172
     */
173 1
    public function show_midcom($path) : bool
174
    {
175
        try {
176 1
            $root_topic = midcom_core_context::get()->get_key(MIDCOM_CONTEXT_ROOTTOPIC);
177 1
            if ($root_topic->style) {
178 1
                $db_style = midcom_db_style::id_from_path($root_topic->style);
179
            }
180
        } catch (midcom_error_forbidden $e) {
181
            $e->log();
182
        }
183
184 1
        $loader = clone $this->loader;
185 1
        $loader->set_directories(null, [MIDCOM_ROOT . '/midcom/style'], []);
186
187 1
        $_style = $loader->get_element($path, $db_style ?? null);
188
189 1
        if ($_style !== null) {
190 1
            $this->render($_style, $path);
191 1
            return true;
192
        }
193
        debug_add("The element '{$path}' could not be found.", MIDCOM_LOG_INFO);
194
        return false;
195
    }
196
197
    /**
198
     * Adds an extra style directory to check for style elements at
199
     * the end of the styledir queue.
200
     *
201
     * @throws midcom_error exception if directory does not exist.
202
     */
203 73
    public function append_styledir(string $dirname)
204
    {
205 73
        if (!file_exists($dirname)) {
206
            throw new midcom_error("Style directory $dirname does not exist!");
207
        }
208 73
        $this->_styledirs_append[midcom_core_context::get()->id][] = $dirname;
209 73
    }
210
211
    /**
212
     * Function prepend styledir
213
     */
214 81
    public function prepend_styledir(string $dirname)
215
    {
216 81
        if (!file_exists($dirname)) {
217
            throw new midcom_error("Style directory {$dirname} does not exist.");
218
        }
219 81
        $this->_styledirs_prepend[midcom_core_context::get()->id][] = $dirname;
220 81
    }
221
222
    /**
223
     * Append the styledir of a component to the queue of styledirs.
224
     */
225
    public function append_component_styledir(string $component)
226
    {
227
        $loader = midcom::get()->componentloader;
228
        $path = $loader->path_to_snippetpath($component) . "/style";
229
        $this->append_styledir($path);
230
    }
231
232
    /**
233
     * Prepend the styledir of a component
234
     */
235 81
    public function prepend_component_styledir(string $component)
236
    {
237 81
        $loader = midcom::get()->componentloader;
238 81
        $path = $loader->path_to_snippetpath($component) . "/style";
239 81
        $this->prepend_styledir($path);
240 81
    }
241
242
    /**
243
     * Appends a substyle after the currently selected component style.
244
     *
245
     * Enables a depth of more than one style during substyle selection.
246
     */
247 31
    public function append_substyle(string $newsub)
248
    {
249
        // Make sure try to use only the first argument if we get space separated list, fixes #1788
250 31
        if (strpos($newsub, ' ') !== false) {
251
            $newsub = preg_replace('/^(.+?) .+/', '$1', $newsub);
252
        }
253
254 31
        $context = midcom_core_context::get();
255 31
        $current_style = $context->get_key(MIDCOM_CONTEXT_SUBSTYLE);
256
257 31
        if (!empty($current_style)) {
258
            $newsub = $current_style . '/' . $newsub;
259
        }
260
261 31
        $context->set_key(MIDCOM_CONTEXT_SUBSTYLE, $newsub);
262 31
    }
263
264
    /**
265
     * Prepends a substyle before the currently selected component style.
266
     *
267
     * Enables a depth of more than one style during substyle selection.
268
     *
269
     * @param string $newsub The substyle to prepend.
270
     */
271
    function prepend_substyle($newsub)
272
    {
273
        $context = midcom_core_context::get();
274
        $current_style = $context->get_key(MIDCOM_CONTEXT_SUBSTYLE);
275
276
        if (!empty($current_style)) {
277
            $newsub .= "/" . $current_style;
278
        }
279
        debug_add("Updating Component Context Substyle from $current_style to $newsub");
280
281
        $context->set_key(MIDCOM_CONTEXT_SUBSTYLE, $newsub);
282
    }
283
284
    /**
285
     * Switches the context (see dynamic load).
286
     *
287
     * Private variables are adjusted, and the prepend and append styles are merged with the componentstyle.
288
     * You cannot change the style stack after that (unless you call enter_context again of course).
289
     *
290
     * @param midcom_core_context $context The context to enter
291
     */
292 268
    public function enter_context(midcom_core_context $context)
293
    {
294
        // set new context and topic
295 268
        array_unshift($this->_context, $context); // push into context stack
296
297 268
        if ($this->_topic = $context->get_key(MIDCOM_CONTEXT_CONTENTTOPIC)) {
0 ignored issues
show
Documentation Bug introduced by
It seems like $context->get_key(MIDCOM_CONTEXT_CONTENTTOPIC) can also be of type false. However, the property $_topic is declared as type midcom_db_topic. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
298 268
            $this->loader->initialize_from_topic($this->_topic, $context);
299
        }
300
301 268
        $this->loader->set_directories(
302 268
            $this->_topic,
303 268
            $this->_styledirs_prepend[$context->id] ?? [],
304 268
            $this->_styledirs_append[$context->id] ?? []
305
        );
306 268
    }
307
308
    /**
309
     * Switches the context (see dynamic load). Private variables $_context, $_topic
310
     * and $loader are adjusted.
311
     *
312
     * @todo check documentation
313
     */
314 268
    public function leave_context()
315
    {
316 268
        array_shift($this->_context);
317 268
        $previous_context = $this->_context[0] ?? midcom_core_context::get();
318
319 268
        $this->_topic = $previous_context->get_key(MIDCOM_CONTEXT_CONTENTTOPIC);
0 ignored issues
show
Documentation Bug introduced by
It seems like $previous_context->get_k...M_CONTEXT_CONTENTTOPIC) can also be of type false. However, the property $_topic is declared as type midcom_db_topic. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
320 268
        $this->loader->set_directories(
321 268
            $this->_topic,
0 ignored issues
show
Bug introduced by
It seems like $this->_topic can also be of type false; however, parameter $topic of midcom\templating\loader::set_directories() does only seem to accept midcom_db_topic|null, 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

321
            /** @scrutinizer ignore-type */ $this->_topic,
Loading history...
322 268
            $this->_styledirs_prepend[$previous_context->id] ?? [],
323 268
            $this->_styledirs_append[$previous_context->id] ?? []
324
        );
325 268
    }
326
}
327