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
|
|
|
private ?midcom_db_topic $_topic = null; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Context stack |
51
|
|
|
* |
52
|
|
|
* @var midcom_core_context[] |
53
|
|
|
*/ |
54
|
|
|
private array $_context = []; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* List of styledirs to handle after componentstyle |
58
|
|
|
*/ |
59
|
|
|
private array $_styledirs_append = []; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* List of styledirs to handle before componentstyle |
63
|
|
|
*/ |
64
|
|
|
private array $_styledirs_prepend = []; |
65
|
|
|
|
66
|
|
|
private loader $loader; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Data to pass to the style |
70
|
|
|
*/ |
71
|
|
|
public array $data; |
72
|
|
|
|
73
|
1 |
|
public function __construct(loader $loader) |
74
|
|
|
{ |
75
|
1 |
|
$this->loader = $loader; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Looks for a style element matching $path (either in a user defined style |
80
|
|
|
* or the default style snippetdir) and displays/evaluates it. |
81
|
|
|
*/ |
82
|
207 |
|
public function show(string $path) : bool |
83
|
|
|
{ |
84
|
207 |
|
if ($this->_context === []) { |
85
|
|
|
debug_add("Trying to show '{$path}' but there is no context set", MIDCOM_LOG_INFO); |
86
|
|
|
return false; |
87
|
|
|
} |
88
|
|
|
|
89
|
207 |
|
$style = $this->loader->get_element($path, true); |
90
|
|
|
|
91
|
207 |
|
if ($style === null) { |
92
|
24 |
|
if ($path == 'ROOT') { |
93
|
|
|
// Go to fallback ROOT instead of displaying a blank page |
94
|
|
|
return $this->show_midcom($path); |
95
|
|
|
} |
96
|
|
|
|
97
|
24 |
|
debug_add("The element '{$path}' could not be found.", MIDCOM_LOG_INFO); |
98
|
24 |
|
return false; |
99
|
|
|
} |
100
|
206 |
|
$this->render($style, $path); |
101
|
|
|
|
102
|
206 |
|
return true; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Renders the style element with current request data |
107
|
|
|
* |
108
|
|
|
* @param string $style The style element content |
109
|
|
|
* @param string $path the element's name |
110
|
|
|
* @throws midcom_error |
111
|
|
|
*/ |
112
|
206 |
|
private function render(string $style, string $path) |
113
|
|
|
{ |
114
|
206 |
|
if (midcom::get()->config->get('wrap_style_show_with_name')) { |
115
|
|
|
$style = "\n<!-- Start of style '{$path}' -->\n" . $style; |
116
|
|
|
$style .= "\n<!-- End of style '{$path}' -->\n"; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
// Resolve variables |
120
|
206 |
|
$preparsed = midcom_helper_formatter::compile($style); |
121
|
|
|
|
122
|
206 |
|
if (midcom_core_context::get()->has_custom_key('request_data')) { |
123
|
206 |
|
$data =& midcom_core_context::get()->get_custom_key('request_data'); |
|
|
|
|
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
try { |
127
|
206 |
|
eval('?>' . $preparsed); |
|
|
|
|
128
|
|
|
} catch (ParseError $e) { |
|
|
|
|
129
|
|
|
throw new midcom_error("Failed to parse style element '{$path}': " . $e->getMessage() . ' in line ' . $e->getLine()); |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Looks for a midcom core style element matching $path and displays/evaluates it. |
135
|
|
|
* This offers a bit reduced functionality and will only look in the DB root style, |
136
|
|
|
* the theme directory and midcom's style directory, because it has to work even when |
137
|
|
|
* midcom is not yet fully initialized |
138
|
|
|
*/ |
139
|
1 |
|
public function show_midcom(string $path) : bool |
140
|
|
|
{ |
141
|
1 |
|
$loader = clone $this->loader; |
142
|
1 |
|
$loader->set_directories(null, [MIDCOM_ROOT . '/midcom/style'], []); |
143
|
|
|
|
144
|
1 |
|
$_style = $loader->get_element($path, false); |
145
|
|
|
|
146
|
1 |
|
if ($_style !== null) { |
147
|
1 |
|
$this->render($_style, $path); |
148
|
1 |
|
return true; |
149
|
|
|
} |
150
|
|
|
debug_add("The element '{$path}' could not be found.", MIDCOM_LOG_INFO); |
151
|
|
|
return false; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Adds an extra style directory to check for style elements at |
156
|
|
|
* the end of the styledir queue. |
157
|
|
|
* |
158
|
|
|
* @throws midcom_error exception if directory does not exist. |
159
|
|
|
*/ |
160
|
77 |
|
public function append_styledir(string $dirname) |
161
|
|
|
{ |
162
|
77 |
|
if (!file_exists($dirname)) { |
163
|
|
|
throw new midcom_error("Style directory $dirname does not exist!"); |
164
|
|
|
} |
165
|
77 |
|
$this->_styledirs_append[midcom_core_context::get()->id][] = $dirname; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Function prepend styledir |
170
|
|
|
*/ |
171
|
83 |
|
public function prepend_styledir(string $dirname) |
172
|
|
|
{ |
173
|
83 |
|
if (!file_exists($dirname)) { |
174
|
|
|
throw new midcom_error("Style directory {$dirname} does not exist."); |
175
|
|
|
} |
176
|
83 |
|
$this->_styledirs_prepend[midcom_core_context::get()->id][] = $dirname; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Append the styledir of a component to the queue of styledirs. |
181
|
|
|
*/ |
182
|
|
|
public function append_component_styledir(string $component) |
183
|
|
|
{ |
184
|
|
|
$path = midcom::get()->componentloader->path_to_snippetpath($component) . "/style"; |
185
|
|
|
$this->append_styledir($path); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Prepend the styledir of a component |
190
|
|
|
*/ |
191
|
83 |
|
public function prepend_component_styledir(string $component) |
192
|
|
|
{ |
193
|
83 |
|
$path = midcom::get()->componentloader->path_to_snippetpath($component) . "/style"; |
194
|
83 |
|
$this->prepend_styledir($path); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* Appends a substyle after the currently selected component style. |
199
|
|
|
* |
200
|
|
|
* Enables a depth of more than one style during substyle selection. |
201
|
|
|
*/ |
202
|
31 |
|
public function append_substyle(string $newsub) |
203
|
|
|
{ |
204
|
|
|
// Make sure try to use only the first argument if we get space separated list, fixes #1788 |
205
|
31 |
|
if (str_contains($newsub, ' ')) { |
206
|
|
|
$newsub = preg_replace('/^(.+?) .+/', '$1', $newsub); |
207
|
|
|
} |
208
|
|
|
|
209
|
31 |
|
$context = midcom_core_context::get(); |
210
|
31 |
|
$current_style = $context->get_key(MIDCOM_CONTEXT_SUBSTYLE); |
211
|
|
|
|
212
|
31 |
|
if (!empty($current_style)) { |
213
|
|
|
$newsub = $current_style . '/' . $newsub; |
214
|
|
|
} |
215
|
|
|
|
216
|
31 |
|
$context->set_key(MIDCOM_CONTEXT_SUBSTYLE, $newsub); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* Prepends a substyle before the currently selected component style. |
221
|
|
|
* |
222
|
|
|
* Enables a depth of more than one style during substyle selection. |
223
|
|
|
* |
224
|
|
|
* @param string $newsub The substyle to prepend. |
225
|
|
|
*/ |
226
|
|
|
function prepend_substyle($newsub) |
|
|
|
|
227
|
|
|
{ |
228
|
|
|
$context = midcom_core_context::get(); |
229
|
|
|
$current_style = $context->get_key(MIDCOM_CONTEXT_SUBSTYLE); |
230
|
|
|
|
231
|
|
|
if (!empty($current_style)) { |
232
|
|
|
$newsub .= "/" . $current_style; |
233
|
|
|
} |
234
|
|
|
debug_add("Updating Component Context Substyle from $current_style to $newsub"); |
235
|
|
|
|
236
|
|
|
$context->set_key(MIDCOM_CONTEXT_SUBSTYLE, $newsub); |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* Switches the context (see dynamic load). |
241
|
|
|
* |
242
|
|
|
* Private variables are adjusted, and the prepend and append styles are merged with the componentstyle. |
243
|
|
|
* You cannot change the style stack after that (unless you call enter_context again of course). |
244
|
|
|
*/ |
245
|
277 |
|
public function enter_context(midcom_core_context $context) |
246
|
|
|
{ |
247
|
|
|
// set new context and topic |
248
|
277 |
|
array_unshift($this->_context, $context); // push into context stack |
249
|
|
|
|
250
|
277 |
|
if ($this->_topic = $context->get_key(MIDCOM_CONTEXT_CONTENTTOPIC)) { |
251
|
277 |
|
$style = $this->_topic->style ?: $context->get_inherited_style(); |
252
|
277 |
|
if (!$style) { |
253
|
277 |
|
$styleengine_default_styles = midcom::get()->config->get('styleengine_default_styles'); |
254
|
277 |
|
if (isset($styleengine_default_styles[$this->_topic->component])) { |
255
|
|
|
$style = $styleengine_default_styles[$this->_topic->component]; |
256
|
|
|
} |
257
|
|
|
} |
258
|
277 |
|
$this->loader->initialize($context, $style); |
259
|
|
|
} |
260
|
|
|
|
261
|
277 |
|
$this->loader->set_directories( |
262
|
277 |
|
$this->_topic, |
263
|
277 |
|
$this->_styledirs_prepend[$context->id] ?? [], |
264
|
277 |
|
$this->_styledirs_append[$context->id] ?? [] |
265
|
277 |
|
); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* Switches the context (see dynamic load). Private variables $_context, $_topic |
270
|
|
|
* and $loader are adjusted. |
271
|
|
|
*/ |
272
|
277 |
|
public function leave_context() |
273
|
|
|
{ |
274
|
277 |
|
array_shift($this->_context); |
275
|
277 |
|
$previous_context = $this->_context[0] ?? midcom_core_context::get(); |
276
|
|
|
|
277
|
277 |
|
$this->_topic = $previous_context->get_key(MIDCOM_CONTEXT_CONTENTTOPIC); |
278
|
277 |
|
$this->loader->set_directories( |
279
|
277 |
|
$this->_topic, |
280
|
277 |
|
$this->_styledirs_prepend[$previous_context->id] ?? [], |
281
|
277 |
|
$this->_styledirs_append[$previous_context->id] ?? [] |
282
|
277 |
|
); |
283
|
|
|
} |
284
|
|
|
} |
285
|
|
|
|