Passed
Push — master ( 1b6466...f0a80d )
by Andreas
24:57
created

midcom_helper_style   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 257
Duplicated Lines 0 %

Test Coverage

Coverage 71.28%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 81
c 1
b 0
f 0
dl 0
loc 257
ccs 67
cts 94
cp 0.7128
rs 10
wmc 28

12 Methods

Rating   Name   Duplication   Size   Complexity  
A show() 0 21 4
A append_styledir() 0 6 2
A prepend_substyle() 0 11 2
A __construct() 0 3 1
A show_midcom() 0 13 2
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 20 5
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(loader $loader)
85
    {
86 1
        $this->loader = $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 205
    public function show(string $path) : bool
94
    {
95 205
        if ($this->_context === []) {
96
            debug_add("Trying to show '{$path}' but there is no context set", MIDCOM_LOG_INFO);
97
            return false;
98
        }
99
100 205
        $style = $this->loader->get_element($path, true);
101
102 205
        if ($style === null) {
103 20
            if ($path == 'ROOT') {
104
                // Go to fallback ROOT instead of displaying a blank page
105
                return $this->show_midcom($path);
106
            }
107
108 20
            debug_add("The element '{$path}' could not be found.", MIDCOM_LOG_INFO);
109 20
            return false;
110
        }
111 204
        $this->render($style, $path);
112
113 204
        return true;
114
    }
115
116
    /**
117
     * Renders the style element with current request data
118
     *
119
     * @param string $style The style element content
120
     * @param string $path the element's name
121
     * @throws midcom_error
122
     */
123 204
    private function render(string $style, string $path)
124
    {
125 204
        if (midcom::get()->config->get('wrap_style_show_with_name')) {
126
            $style = "\n<!-- Start of style '{$path}' -->\n" . $style;
127
            $style .= "\n<!-- End of style '{$path}' -->\n";
128
        }
129
130
        // Resolve variables
131 204
        $preparsed = midcom_helper_formatter::compile($style);
132
133 204
        if (midcom_core_context::get()->has_custom_key('request_data')) {
134 204
            $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...
135
        }
136
137
        try {
138 204
            eval('?>' . $preparsed);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
139
        } 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...
140
            throw new midcom_error("Failed to parse style element '{$path}': " . $e->getMessage() . ' in line ' . $e->getLine());
141
        }
142 204
    }
143
144
    /**
145
     * Looks for a midcom core style element matching $path and displays/evaluates it.
146
     * This offers a bit reduced functionality and will only look in the DB root style,
147
     * the theme directory and midcom's style directory, because it has to work even when
148
     * midcom is not yet fully initialized
149
     */
150 1
    public function show_midcom(string $path) : bool
151
    {
152 1
        $loader = clone $this->loader;
153 1
        $loader->set_directories(null, [MIDCOM_ROOT . '/midcom/style'], []);
154
155 1
        $_style = $loader->get_element($path, false);
156
157 1
        if ($_style !== null) {
158 1
            $this->render($_style, $path);
159 1
            return true;
160
        }
161
        debug_add("The element '{$path}' could not be found.", MIDCOM_LOG_INFO);
162
        return false;
163
    }
164
165
    /**
166
     * Adds an extra style directory to check for style elements at
167
     * the end of the styledir queue.
168
     *
169
     * @throws midcom_error exception if directory does not exist.
170
     */
171 73
    public function append_styledir(string $dirname)
172
    {
173 73
        if (!file_exists($dirname)) {
174
            throw new midcom_error("Style directory $dirname does not exist!");
175
        }
176 73
        $this->_styledirs_append[midcom_core_context::get()->id][] = $dirname;
177 73
    }
178
179
    /**
180
     * Function prepend styledir
181
     */
182 84
    public function prepend_styledir(string $dirname)
183
    {
184 84
        if (!file_exists($dirname)) {
185
            throw new midcom_error("Style directory {$dirname} does not exist.");
186
        }
187 84
        $this->_styledirs_prepend[midcom_core_context::get()->id][] = $dirname;
188 84
    }
189
190
    /**
191
     * Append the styledir of a component to the queue of styledirs.
192
     */
193
    public function append_component_styledir(string $component)
194
    {
195
        $loader = midcom::get()->componentloader;
196
        $path = $loader->path_to_snippetpath($component) . "/style";
197
        $this->append_styledir($path);
198
    }
199
200
    /**
201
     * Prepend the styledir of a component
202
     */
203 84
    public function prepend_component_styledir(string $component)
204
    {
205 84
        $loader = midcom::get()->componentloader;
206 84
        $path = $loader->path_to_snippetpath($component) . "/style";
207 84
        $this->prepend_styledir($path);
208 84
    }
209
210
    /**
211
     * Appends a substyle after the currently selected component style.
212
     *
213
     * Enables a depth of more than one style during substyle selection.
214
     */
215 31
    public function append_substyle(string $newsub)
216
    {
217
        // Make sure try to use only the first argument if we get space separated list, fixes #1788
218 31
        if (str_contains($newsub, ' ')) {
219
            $newsub = preg_replace('/^(.+?) .+/', '$1', $newsub);
220
        }
221
222 31
        $context = midcom_core_context::get();
223 31
        $current_style = $context->get_key(MIDCOM_CONTEXT_SUBSTYLE);
224
225 31
        if (!empty($current_style)) {
226
            $newsub = $current_style . '/' . $newsub;
227
        }
228
229 31
        $context->set_key(MIDCOM_CONTEXT_SUBSTYLE, $newsub);
230 31
    }
231
232
    /**
233
     * Prepends a substyle before the currently selected component style.
234
     *
235
     * Enables a depth of more than one style during substyle selection.
236
     *
237
     * @param string $newsub The substyle to prepend.
238
     */
239
    function prepend_substyle($newsub)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
240
    {
241
        $context = midcom_core_context::get();
242
        $current_style = $context->get_key(MIDCOM_CONTEXT_SUBSTYLE);
243
244
        if (!empty($current_style)) {
245
            $newsub .= "/" . $current_style;
246
        }
247
        debug_add("Updating Component Context Substyle from $current_style to $newsub");
248
249
        $context->set_key(MIDCOM_CONTEXT_SUBSTYLE, $newsub);
250
    }
251
252
    /**
253
     * Switches the context (see dynamic load).
254
     *
255
     * Private variables are adjusted, and the prepend and append styles are merged with the componentstyle.
256
     * You cannot change the style stack after that (unless you call enter_context again of course).
257
     *
258
     * @param midcom_core_context $context The context to enter
259
     */
260 271
    public function enter_context(midcom_core_context $context)
261
    {
262
        // set new context and topic
263 271
        array_unshift($this->_context, $context); // push into context stack
264
265 271
        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...
266 271
            $style = $this->_topic->style ?: $context->get_inherited_style();
267 271
            if (!$style) {
268 271
                $styleengine_default_styles = midcom::get()->config->get('styleengine_default_styles');
269 271
                if (isset($styleengine_default_styles[$this->_topic->component])) {
270
                    $style = $styleengine_default_styles[$this->_topic->component];
271
                }
272
            }
273 271
            $this->loader->initialize($context, $style);
274
        }
275
276 271
        $this->loader->set_directories(
277 271
            $this->_topic,
278 271
            $this->_styledirs_prepend[$context->id] ?? [],
279 271
            $this->_styledirs_append[$context->id] ?? []
280
        );
281 271
    }
282
283
    /**
284
     * Switches the context (see dynamic load). Private variables $_context, $_topic
285
     * and $loader are adjusted.
286
     *
287
     * @todo check documentation
288
     */
289 271
    public function leave_context()
290
    {
291 271
        array_shift($this->_context);
292 271
        $previous_context = $this->_context[0] ?? midcom_core_context::get();
293
294 271
        $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...
295 271
        $this->loader->set_directories(
296 271
            $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

296
            /** @scrutinizer ignore-type */ $this->_topic,
Loading history...
297 271
            $this->_styledirs_prepend[$previous_context->id] ?? [],
298 271
            $this->_styledirs_append[$previous_context->id] ?? []
299
        );
300 271
    }
301
}
302