Passed
Push — master ( 9eeb8b...1fec05 )
by Andreas
16:44
created

midcom_helper_style::show_midcom()   A

Complexity

Conditions 6
Paths 20

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6.3357

Importance

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

335
            /** @scrutinizer ignore-type */ $this->_topic,
Loading history...
336 268
            $this->_styledirs_prepend[$previous_context->id] ?? [],
337 268
            $this->_styledirs_append[$previous_context->id] ?? []
338
        );
339 268
    }
340
}
341