1
|
|
|
<?php |
|
|
|
|
2
|
|
|
|
3
|
|
|
include_once(MODX_BASE_PATH . 'assets/lib/APIHelpers.class.php'); |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Class DLTemplate |
7
|
|
|
*/ |
8
|
|
|
class DLTemplate |
|
|
|
|
9
|
|
|
{ |
10
|
|
|
/** |
11
|
|
|
* Объект DocumentParser - основной класс MODX |
12
|
|
|
* @var \DocumentParser |
13
|
|
|
* @access protected |
14
|
|
|
*/ |
15
|
|
|
protected $modx = null; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* @var DLTemplate cached reference to singleton instance |
19
|
|
|
*/ |
20
|
|
|
protected static $instance; |
21
|
|
|
|
22
|
|
|
protected $templatePath = 'assets/templates/'; |
23
|
|
|
|
24
|
|
|
protected $templateExtension = 'html'; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @var null|Twig_Environment twig object |
28
|
|
|
*/ |
29
|
|
|
protected $twig = null; |
30
|
|
|
|
31
|
|
|
protected $twigEnabled = false; |
32
|
|
|
|
33
|
|
|
protected $twigTemplateVars = array(); |
34
|
|
|
|
35
|
|
|
public $phx = null; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* gets the instance via lazy initialization (created on first usage) |
39
|
|
|
* |
40
|
|
|
* @return self |
41
|
|
|
*/ |
42
|
|
|
public static function getInstance(DocumentParser $modx) |
43
|
|
|
{ |
44
|
|
|
|
45
|
|
|
if (null === self::$instance) { |
46
|
|
|
self::$instance = new self($modx); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
return self::$instance; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* is not allowed to call from outside: private! |
54
|
|
|
* |
55
|
|
|
*/ |
56
|
|
|
private function __construct(DocumentParser $modx) |
57
|
|
|
{ |
58
|
|
|
$this->modx = $modx; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* prevent the instance from being cloned |
63
|
|
|
* |
64
|
|
|
* @return void |
65
|
|
|
*/ |
66
|
|
|
private function __clone() |
67
|
|
|
{ |
68
|
|
|
|
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* prevent from being unserialized |
73
|
|
|
* |
74
|
|
|
* @return void |
75
|
|
|
*/ |
76
|
|
|
private function __wakeup() |
77
|
|
|
{ |
78
|
|
|
|
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Задает относительный путь к папке с шаблонами |
83
|
|
|
* |
84
|
|
|
* @param $path |
85
|
|
|
* @return $this |
86
|
|
|
*/ |
87
|
|
|
public function setTemplatePath($path) |
88
|
|
|
{ |
89
|
|
|
$path = trim($path); |
90
|
|
|
$path = preg_replace(array( |
91
|
|
|
'/\.*[\/|\\\]/i', |
92
|
|
|
'/[\/|\\\]+/i' |
93
|
|
|
), array('/', '/'), $path); |
94
|
|
|
|
95
|
|
|
if (!empty($path)) { |
96
|
|
|
$this->templatePath = $path; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
return $this; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Задает расширение файла с шаблоном |
104
|
|
|
* |
105
|
|
|
* @param $ext |
106
|
|
|
* @return $this |
107
|
|
|
*/ |
108
|
|
|
public function setTemplateExtension($ext) |
109
|
|
|
{ |
110
|
|
|
$ext = trim($ext, ". \t\n\r\0\x0B"); |
111
|
|
|
if (!empty($ext)) { |
112
|
|
|
$this->templateExtension = $ext; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
return $this; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Additional data for twig templates |
120
|
|
|
* |
121
|
|
|
* @param array $data |
122
|
|
|
* @return $this |
123
|
|
|
*/ |
124
|
|
|
public function setTwigTemplateVars($data = array()) { |
125
|
|
|
if (is_array($data)) $this->twigTemplateVars = $data; |
126
|
|
|
return $this; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Сохранение данных в массив плейсхолдеров |
131
|
|
|
* |
132
|
|
|
* @param mixed $data данные |
133
|
|
|
* @param int $set устанавливать ли глобальнй плейсхолдер MODX |
134
|
|
|
* @param string $key ключ локального плейсхолдера |
135
|
|
|
* @param string $prefix префикс для ключей массива |
136
|
|
|
* @return string |
137
|
|
|
*/ |
138
|
|
|
public function toPlaceholders($data, $set = 0, $key = 'contentPlaceholder', $prefix = '') |
139
|
|
|
{ |
140
|
|
|
$out = ''; |
141
|
|
|
if ($set != 0) { |
142
|
|
|
$this->modx->toPlaceholder($key, $data, $prefix); |
143
|
|
|
} else { |
144
|
|
|
$out = $data; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
return $out; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* refactor $modx->getChunk(); |
152
|
|
|
* |
153
|
|
|
* @param string $name Template: chunk name || @CODE: template || @FILE: file with template |
154
|
|
|
* @return string html template with placeholders without data |
155
|
|
|
*/ |
156
|
|
|
public function getChunk($name) |
157
|
|
|
{ |
158
|
|
|
$tpl = ''; |
159
|
|
|
$this->twigEnabled = substr($name,0,3) == '@T_'; |
160
|
|
|
if ($name != '' && !isset($this->modx->chunkCache[$name])) { |
161
|
|
|
$mode = (preg_match('/^((@[A-Z_]+)[:]{0,1})(.*)/Asu', trim($name), |
162
|
|
|
$tmp) && isset($tmp[2], $tmp[3])) ? $tmp[2] : false; |
163
|
|
|
$subTmp = (isset($tmp[3])) ? trim($tmp[3]) : null; |
164
|
|
|
if ($this->twigEnabled) $mode = '@'.substr($mode,3); |
165
|
|
|
switch ($mode) { |
166
|
|
|
case '@FILE': |
167
|
|
|
if ($subTmp != '') { |
168
|
|
|
$real = realpath(MODX_BASE_PATH . $this->templatePath); |
169
|
|
|
$path = realpath(MODX_BASE_PATH . $this->templatePath . preg_replace(array( |
170
|
|
|
'/\.*[\/|\\\]/i', |
171
|
|
|
'/[\/|\\\]+/i' |
172
|
|
|
), array('/', '/'), $subTmp) . '.' . $this->templateExtension); |
173
|
|
|
$fname = explode(".", $path); |
174
|
|
|
if ($real == substr($path, 0, |
175
|
|
|
strlen($real)) && end($fname) == $this->templateExtension && file_exists($path) |
176
|
|
|
) { |
177
|
|
|
$tpl = file_get_contents($path); |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
break; |
181
|
|
|
case '@CHUNK': |
182
|
|
|
if ($subTmp != '') { |
183
|
|
|
$tpl = $this->modx->getChunk($subTmp); |
184
|
|
|
} |
185
|
|
|
break; |
186
|
|
|
case '@INLINE': |
187
|
|
|
case '@TPL': |
188
|
|
|
case '@CODE': |
189
|
|
|
$tpl = $tmp[3]; //without trim |
190
|
|
|
break; |
191
|
|
|
case '@DOCUMENT': |
192
|
|
|
case '@DOC': |
193
|
|
|
switch (true) { |
194
|
|
|
case ((int)$subTmp > 0): |
195
|
|
|
$tpl = $this->modx->getPageInfo((int)$subTmp, 0, "content"); |
196
|
|
|
$tpl = isset($tpl['content']) ? $tpl['content'] : ''; |
197
|
|
|
break; |
198
|
|
|
case ((int)$subTmp == 0): |
199
|
|
|
$tpl = $this->modx->documentObject['content']; |
200
|
|
|
break; |
201
|
|
|
} |
202
|
|
|
break; |
203
|
|
|
case '@PLH': |
204
|
|
|
case '@PLACEHOLDER': |
205
|
|
|
if ($subTmp != '') { |
206
|
|
|
$tpl = $this->modx->getPlaceholder($subTmp); |
207
|
|
|
} |
208
|
|
|
break; |
209
|
|
|
case '@CFG': |
210
|
|
|
case '@CONFIG': |
211
|
|
|
case '@OPTIONS': |
212
|
|
|
if ($subTmp != '') { |
213
|
|
|
$tpl = $this->modx->getConfig($subTmp); |
214
|
|
|
} |
215
|
|
|
break; |
216
|
|
|
case '@SNIPPET': |
217
|
|
|
if ($subTmp != '') { |
218
|
|
|
$tpl = $this->modx->runSnippet($subTmp, $this->modx->event->params); |
219
|
|
|
} |
220
|
|
|
break; |
221
|
|
|
case '@RENDERPAGE': |
222
|
|
|
$tpl = $this->renderDoc($subTmp, false); |
223
|
|
|
break; |
224
|
|
|
case '@LOADPAGE': |
225
|
|
|
$tpl = $this->renderDoc($subTmp, true); |
226
|
|
|
break; |
227
|
|
|
case '@TEMPLATE': |
228
|
|
|
$tpl = $this->getTemplate($subTmp); |
229
|
|
|
break; |
230
|
|
|
default: |
231
|
|
|
$tpl = $this->modx->getChunk($name); |
232
|
|
|
} |
233
|
|
|
$this->modx->chunkCache[$name] = $tpl; |
234
|
|
|
} else { |
235
|
|
|
if ($name != '') { |
236
|
|
|
$tpl = $this->modx->getChunk($name); |
237
|
|
|
} |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
return $tpl; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Рендер документа с подстановкой плейсхолдеров и выполнением сниппетов |
245
|
|
|
* |
246
|
|
|
* @param int $id ID документа |
247
|
|
|
* @param bool $events Во время рендера документа стоит ли вызывать события OnLoadWebDocument и OnLoadDocumentObject (внутри метода getDocumentObject). |
248
|
|
|
* @param mixed $tpl Шаблон с которым необходимо отрендерить документ. Возможные значения: |
249
|
|
|
* null - Использовать шаблон который назначен документу |
250
|
|
|
* int(0-n) - Получить шаблон из базы данных с указанным ID и применить его к документу |
251
|
|
|
* string - Применить шаблон указанный в строке к документу |
252
|
|
|
* @return string |
253
|
|
|
* |
254
|
|
|
* Событие OnLoadWebDocument дополнительно передает параметры: |
255
|
|
|
* - с источиком от куда произошел вызов события |
256
|
|
|
* - оригинальный экземпляр класса DocumentParser |
257
|
|
|
*/ |
258
|
|
|
public function renderDoc($id, $events = false, $tpl = null) |
259
|
|
|
{ |
260
|
|
|
if ((int)$id <= 0) { |
261
|
|
|
return ''; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
$m = clone $this->modx; //Чтобы была возможность вызывать события |
265
|
|
|
$m->documentObject = $m->getDocumentObject('id', (int)$id, $events ? 'prepareResponse' : null); |
266
|
|
|
if ($m->documentObject['type'] == "reference") { |
267
|
|
|
if (is_numeric($m->documentObject['content']) && $m->documentObject['content'] > 0) { |
268
|
|
|
$m->documentObject['content'] = $this->renderDoc($m->documentObject['content'], $events); |
269
|
|
|
} |
270
|
|
|
} |
271
|
|
|
switch (true) { |
272
|
|
|
case is_integer($tpl): |
273
|
|
|
$tpl = $this->getTemplate($tpl); |
274
|
|
|
break; |
275
|
|
|
case is_string($tpl): |
276
|
|
|
break; |
277
|
|
|
case is_null($tpl): |
278
|
|
|
default: |
279
|
|
|
$tpl = $this->getTemplate($m->documentObject['template']); |
280
|
|
|
} |
281
|
|
|
$m->documentContent = $tpl; |
282
|
|
|
if ($events) { |
283
|
|
|
$m->invokeEvent("OnLoadWebDocument", array( |
284
|
|
|
'source' => 'DLTemplate', |
285
|
|
|
'mainModx' => $this->modx, |
286
|
|
|
)); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
return $this->parseDocumentSource($m->documentContent, $m); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Получить содержимое шаблона с определенным номером |
294
|
|
|
* @param int $id Номер шаблона |
295
|
|
|
* @return string HTML код шаблона |
296
|
|
|
*/ |
297
|
|
|
public function getTemplate($id) |
298
|
|
|
{ |
299
|
|
|
$tpl = null; |
300
|
|
|
if ($id > 0) { |
301
|
|
|
$tpl = $this->modx->db->getValue("SELECT `content` FROM {$this->modx->getFullTableName("site_templates")} WHERE `id` = '{$id}'"); |
302
|
|
|
} |
303
|
|
|
if (is_null($tpl)) { |
304
|
|
|
$tpl = '[*content*]'; |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
return $tpl; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* refactor $modx->parseChunk(); |
312
|
|
|
* |
313
|
|
|
* @param string $name Template: chunk name || @CODE: template || @FILE: file with template |
314
|
|
|
* @param array $data paceholder |
315
|
|
|
* @param bool $parseDocumentSource render html template via DocumentParser::parseDocumentSource() |
316
|
|
|
* @return string html template with data without placeholders |
317
|
|
|
*/ |
318
|
|
|
public function parseChunk($name, $data = array(), $parseDocumentSource = false) |
319
|
|
|
{ |
320
|
|
|
$out = $this->getChunk($name); |
321
|
|
|
if ($this->twigEnabled && ($out != '') && ($twig = $this->getTwig($name, $out))) { |
322
|
|
|
$plh = $this->twigTemplateVars; |
323
|
|
|
$plh['data'] = $data; |
324
|
|
|
$plh['modx'] = $this->modx; |
325
|
|
|
$out = $twig->render(md5($name),$plh); |
326
|
|
|
} else { |
327
|
|
|
if (is_array($data) && ($out != '')) { |
328
|
|
|
if (preg_match("/\[\+[A-Z0-9\.\_\-]+\+\]/is", $out)) { |
329
|
|
|
$item = $this->renameKeyArr($data, '[', ']', '+'); |
330
|
|
|
$out = str_replace(array_keys($item), array_values($item), $out); |
331
|
|
|
} |
332
|
|
|
if (preg_match("/:([^:=]+)(?:=`(.*?)`(?=:[^:=]+|$))?/is", $out)) { |
333
|
|
|
if (is_null($this->phx) || !($this->phx instanceof DLphx)) { |
334
|
|
|
$this->phx = $this->createPHx(0, 1000); |
335
|
|
|
} |
336
|
|
|
$this->phx->placeholders = array(); |
337
|
|
|
$this->setPHxPlaceholders($data); |
338
|
|
|
$out = $this->phx->Parse($out); |
339
|
|
|
$out = $this->cleanPHx($out); |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
} |
343
|
|
|
if ($parseDocumentSource) { |
344
|
|
|
$out = $this->parseDocumentSource($out); |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
return $out; |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
/** |
351
|
|
|
* |
352
|
|
|
* @param string|array $value |
353
|
|
|
* @param string $key |
354
|
|
|
* @param string $path |
355
|
|
|
*/ |
356
|
|
|
public function setPHxPlaceholders($value = '', $key = '', $path = '') |
357
|
|
|
{ |
358
|
|
|
$keypath = !empty($path) ? $path . "." . $key : $key; |
359
|
|
|
$this->phx->curPass = 0; |
360
|
|
|
if (is_array($value)) { |
361
|
|
|
foreach ($value as $subkey => $subval) { |
362
|
|
|
$this->setPHxPlaceholders($subval, $subkey, $keypath); |
363
|
|
|
} |
364
|
|
|
} else { |
365
|
|
|
$this->phx->setPHxVariable($keypath, $value); |
366
|
|
|
} |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
/** |
370
|
|
|
* Return clone of twig |
371
|
|
|
* |
372
|
|
|
* @return null |
373
|
|
|
*/ |
374
|
|
|
protected function getTwig($name, $tpl) { |
375
|
|
|
if (is_null($this->twig) && isset($this->modx->twig)) { |
376
|
|
|
$twig = clone $this->modx->twig; |
377
|
|
|
$this->twig = $twig; |
378
|
|
|
} else { |
379
|
|
|
$twig = $this->twig; |
380
|
|
|
} |
381
|
|
|
if ($twig && class_exists('Twig_Loader_Array')) { |
382
|
|
|
$twig->getLoader()->addLoader( |
383
|
|
|
new Twig_Loader_Array(array( |
384
|
|
|
md5($name) => $tpl |
385
|
|
|
)) |
386
|
|
|
); |
387
|
|
|
} |
388
|
|
|
return $twig; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* |
393
|
|
|
* @param string $string |
394
|
|
|
* @return string |
395
|
|
|
*/ |
396
|
|
|
public function cleanPHx($string) |
397
|
|
|
{ |
398
|
|
|
preg_match_all('~\[(\+|\*|\()([^:\+\[\]]+)([^\[\]]*?)(\1|\))\]~s', $string, $matches); |
399
|
|
|
if ($matches[0]) { |
400
|
|
|
$string = str_replace($matches[0], '', $string); |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
return $string; |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
/** |
407
|
|
|
* @param int $debug |
408
|
|
|
* @param int $maxpass |
409
|
|
|
* @return DLphx |
410
|
|
|
*/ |
411
|
|
|
public function createPHx($debug = 0, $maxpass = 50) |
412
|
|
|
{ |
413
|
|
|
if (!class_exists('DLphx', false)) { |
414
|
|
|
include_once(__DIR__ . '/DLphx.class.php'); |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
return new DLphx($debug, $maxpass); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* Переменовывание элементов массива |
422
|
|
|
* |
423
|
|
|
* @param array $data массив с данными |
424
|
|
|
* @param string $prefix префикс ключей |
425
|
|
|
* @param string $suffix суффикс ключей |
426
|
|
|
* @param string $sep разделитель суффиксов, префиксов и ключей массива |
427
|
|
|
* @return array массив с переименованными ключами |
428
|
|
|
*/ |
429
|
|
|
public function renameKeyArr($data, $prefix = '', $suffix = '', $sep = '.') |
430
|
|
|
{ |
431
|
|
|
return APIhelpers::renameKeyArr($data, $prefix, $suffix, $sep); |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
/** |
435
|
|
|
* @param $out |
436
|
|
|
* @param DocumentParser|null $modx |
437
|
|
|
* @return mixed|string |
438
|
|
|
*/ |
439
|
|
|
public function parseDocumentSource($out, $modx = null) |
440
|
|
|
{ |
441
|
|
|
if (!is_object($modx)) { |
442
|
|
|
$modx = $this->modx; |
443
|
|
|
} |
444
|
|
|
$minPasses = empty ($modx->minParserPasses) ? 2 : $modx->minParserPasses; |
445
|
|
|
$maxPasses = empty ($modx->maxParserPasses) ? 10 : $modx->maxParserPasses; |
446
|
|
|
$site_status = $modx->getConfig('site_status'); |
447
|
|
|
$modx->config['site_status'] = 0; |
448
|
|
|
for ($i = 1; $i <= $maxPasses; $i++) { |
449
|
|
|
$html = $out; |
450
|
|
|
if (preg_match('/\[\!(.*)\!\]/us', $out)) { |
451
|
|
|
$out = str_replace(array('[!', '!]'), array('[[', ']]'), $out); |
452
|
|
|
} |
453
|
|
|
if ($i <= $minPasses || $out != $html) { |
454
|
|
|
$out = $modx->parseDocumentSource($out); |
455
|
|
|
} else { |
456
|
|
|
break; |
457
|
|
|
} |
458
|
|
|
} |
459
|
|
|
$out = $modx->rewriteUrls($out); |
460
|
|
|
$modx->config['site_status'] = $site_status; |
461
|
|
|
|
462
|
|
|
return $out; |
463
|
|
|
} |
464
|
|
|
} |
465
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.