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

midcom_helper_style   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Test Coverage

Coverage 70.3%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 88
dl 0
loc 283
ccs 71
cts 101
cp 0.703
rs 10
c 1
b 0
f 0
wmc 29

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A show() 0 21 4
A load() 0 11 2
A append_styledir() 0 6 2
A prepend_substyle() 0 11 2
A show_midcom() 0 22 4
A append_substyle() 0 15 3
A prepend_styledir() 0 6 2
A render() 0 18 4
A append_component_styledir() 0 5 1
A enter_context() 0 13 2
A leave_context() 0 10 1
A prepend_component_styledir() 0 5 1
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
        // Resolve variables
152 201
        $preparsed = midcom_helper_formatter::compile($style);
153
154 201
        if (midcom_core_context::get()->has_custom_key('request_data')) {
155 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...
156
        }
157
158
        try {
159 201
            eval('?>' . $preparsed);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
160
        } catch (ParseError $e) {
0 ignored issues
show
Unused Code introduced by
catch (\ParseError $e) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
161
            throw new midcom_error("Failed to parse style element '{$path}': " . $e->getMessage() . ' in line ' . $e->getLine());
162
        }
163 201
    }
164
165
    /**
166
     * Looks for a midcom core style element matching $path and displays/evaluates it.
167
     * This offers a bit reduced functionality and will only look in the DB root style,
168
     * the theme directory and midcom's style directory, because it has to work even when
169
     * midcom is not yet fully initialized
170
     *
171
     * @param string $path    The style element to show.
172
     * @return boolean            True on success, false otherwise.
173
     */
174 1
    public function show_midcom($path) : bool
175
    {
176
        try {
177 1
            $root_topic = midcom_core_context::get()->get_key(MIDCOM_CONTEXT_ROOTTOPIC);
178 1
            if ($root_topic->style) {
179 1
                $db_style = midcom_db_style::id_from_path($root_topic->style);
180
            }
181
        } catch (midcom_error_forbidden $e) {
182
            $e->log();
183
        }
184
185 1
        $loader = clone $this->loader;
186 1
        $loader->set_directories(null, [MIDCOM_ROOT . '/midcom/style'], []);
187
188 1
        $_style = $loader->get_element($path, $db_style ?? null);
189
190 1
        if ($_style !== null) {
191 1
            $this->render($_style, $path);
192 1
            return true;
193
        }
194
        debug_add("The element '{$path}' could not be found.", MIDCOM_LOG_INFO);
195
        return false;
196
    }
197
198
    /**
199
     * Adds an extra style directory to check for style elements at
200
     * the end of the styledir queue.
201
     *
202
     * @throws midcom_error exception if directory does not exist.
203
     */
204 73
    public function append_styledir(string $dirname)
205
    {
206 73
        if (!file_exists($dirname)) {
207
            throw new midcom_error("Style directory $dirname does not exist!");
208
        }
209 73
        $this->_styledirs_append[midcom_core_context::get()->id][] = $dirname;
210 73
    }
211
212
    /**
213
     * Function prepend styledir
214
     */
215 81
    public function prepend_styledir(string $dirname)
216
    {
217 81
        if (!file_exists($dirname)) {
218
            throw new midcom_error("Style directory {$dirname} does not exist.");
219
        }
220 81
        $this->_styledirs_prepend[midcom_core_context::get()->id][] = $dirname;
221 81
    }
222
223
    /**
224
     * Append the styledir of a component to the queue of styledirs.
225
     */
226
    public function append_component_styledir(string $component)
227
    {
228
        $loader = midcom::get()->componentloader;
229
        $path = $loader->path_to_snippetpath($component) . "/style";
230
        $this->append_styledir($path);
231
    }
232
233
    /**
234
     * Prepend the styledir of a component
235
     */
236 81
    public function prepend_component_styledir(string $component)
237
    {
238 81
        $loader = midcom::get()->componentloader;
239 81
        $path = $loader->path_to_snippetpath($component) . "/style";
240 81
        $this->prepend_styledir($path);
241 81
    }
242
243
    /**
244
     * Appends a substyle after the currently selected component style.
245
     *
246
     * Enables a depth of more than one style during substyle selection.
247
     */
248 31
    public function append_substyle(string $newsub)
249
    {
250
        // Make sure try to use only the first argument if we get space separated list, fixes #1788
251 31
        if (strpos($newsub, ' ') !== false) {
252
            $newsub = preg_replace('/^(.+?) .+/', '$1', $newsub);
253
        }
254
255 31
        $context = midcom_core_context::get();
256 31
        $current_style = $context->get_key(MIDCOM_CONTEXT_SUBSTYLE);
257
258 31
        if (!empty($current_style)) {
259
            $newsub = $current_style . '/' . $newsub;
260
        }
261
262 31
        $context->set_key(MIDCOM_CONTEXT_SUBSTYLE, $newsub);
263 31
    }
264
265
    /**
266
     * Prepends a substyle before the currently selected component style.
267
     *
268
     * Enables a depth of more than one style during substyle selection.
269
     *
270
     * @param string $newsub The substyle to prepend.
271
     */
272
    function prepend_substyle($newsub)
273
    {
274
        $context = midcom_core_context::get();
275
        $current_style = $context->get_key(MIDCOM_CONTEXT_SUBSTYLE);
276
277
        if (!empty($current_style)) {
278
            $newsub .= "/" . $current_style;
279
        }
280
        debug_add("Updating Component Context Substyle from $current_style to $newsub");
281
282
        $context->set_key(MIDCOM_CONTEXT_SUBSTYLE, $newsub);
283
    }
284
285
    /**
286
     * Switches the context (see dynamic load).
287
     *
288
     * Private variables are adjusted, and the prepend and append styles are merged with the componentstyle.
289
     * You cannot change the style stack after that (unless you call enter_context again of course).
290
     *
291
     * @param midcom_core_context $context The context to enter
292
     */
293 268
    public function enter_context(midcom_core_context $context)
294
    {
295
        // set new context and topic
296 268
        array_unshift($this->_context, $context); // push into context stack
297
298 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...
299 268
            $this->loader->initialize_from_topic($this->_topic, $context);
300
        }
301
302 268
        $this->loader->set_directories(
303 268
            $this->_topic,
304 268
            $this->_styledirs_prepend[$context->id] ?? [],
305 268
            $this->_styledirs_append[$context->id] ?? []
306
        );
307 268
    }
308
309
    /**
310
     * Switches the context (see dynamic load). Private variables $_context, $_topic
311
     * and $loader are adjusted.
312
     *
313
     * @todo check documentation
314
     */
315 268
    public function leave_context()
316
    {
317 268
        array_shift($this->_context);
318 268
        $previous_context = $this->_context[0] ?? midcom_core_context::get();
319
320 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...
321 268
        $this->loader->set_directories(
322 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

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