1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @package Theme editor |
5
|
|
|
* @author Iurii Makukh <[email protected]> |
6
|
|
|
* @copyright Copyright (c) 2015, Iurii Makukh |
7
|
|
|
* @license https://www.gnu.org/licenses/gpl.html GNU/GPLv3 |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
namespace gplcart\modules\editor\controllers; |
11
|
|
|
|
12
|
|
|
use gplcart\core\models\Module as ModuleModel, |
13
|
|
|
gplcart\modules\editor\models\Editor as EditorModuleModel; |
14
|
|
|
use gplcart\core\controllers\backend\Controller as BackendController; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Handles incoming requests and outputs data related to Theme editor module |
18
|
|
|
*/ |
19
|
|
|
class Editor extends BackendController |
20
|
|
|
{ |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Editor model instance |
24
|
|
|
* @var \gplcart\modules\editor\models\Editor $editor |
25
|
|
|
*/ |
26
|
|
|
protected $editor; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Module model instance |
30
|
|
|
* @var \gplcart\core\models\Module $module |
31
|
|
|
*/ |
32
|
|
|
protected $module; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* The current module |
36
|
|
|
* @var array |
37
|
|
|
*/ |
38
|
|
|
protected $data_module = array(); |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* The current module file |
42
|
|
|
* @var string |
43
|
|
|
*/ |
44
|
|
|
protected $data_file; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @param EditorModuleModel $editor |
48
|
|
|
* @param ModuleModel $module |
49
|
|
|
*/ |
50
|
|
|
public function __construct(EditorModuleModel $editor, ModuleModel $module) |
51
|
|
|
{ |
52
|
|
|
parent::__construct(); |
53
|
|
|
|
54
|
|
|
$this->editor = $editor; |
55
|
|
|
$this->module = $module; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Route callback to display the select theme page |
60
|
|
|
*/ |
61
|
|
|
public function themeEditor() |
62
|
|
|
{ |
63
|
|
|
$this->setTitleThemeEditor(); |
64
|
|
|
$this->setBreadcrumbThemeEditor(); |
65
|
|
|
|
66
|
|
|
$this->setData('themes', $this->module->getByType('theme')); |
67
|
|
|
|
68
|
|
|
$this->outputThemeEditor(); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Set title on the select theme page |
73
|
|
|
*/ |
74
|
|
|
protected function setTitleThemeEditor() |
75
|
|
|
{ |
76
|
|
|
$this->setTitle($this->text('Themes')); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Set breadcrumbs on the select theme page |
81
|
|
|
*/ |
82
|
|
|
protected function setBreadcrumbThemeEditor() |
83
|
|
|
{ |
84
|
|
|
$breadcrumbs = array(); |
85
|
|
|
|
86
|
|
|
$breadcrumbs[] = array( |
87
|
|
|
'url' => $this->url('admin'), |
88
|
|
|
'text' => $this->text('Dashboard') |
89
|
|
|
); |
90
|
|
|
|
91
|
|
|
$breadcrumbs[] = array( |
92
|
|
|
'url' => $this->url('admin/module/list'), |
93
|
|
|
'text' => $this->text('Modules') |
94
|
|
|
); |
95
|
|
|
|
96
|
|
|
$this->setBreadcrumbs($breadcrumbs); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Output rendered templates on the select theme page |
101
|
|
|
*/ |
102
|
|
|
protected function outputThemeEditor() |
103
|
|
|
{ |
104
|
|
|
$this->output('editor|themes'); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Displays the module file overview page |
109
|
|
|
* @param integer $module_id |
110
|
|
|
*/ |
111
|
|
|
public function listEditor($module_id) |
112
|
|
|
{ |
113
|
|
|
$this->setModuleEditor($module_id); |
114
|
|
|
|
115
|
|
|
$this->setTitleListEditor(); |
116
|
|
|
$this->setBreadcrumbListEditor(); |
117
|
|
|
|
118
|
|
|
$this->setData('module', $this->data_module); |
119
|
|
|
$this->setData('files', $this->getFilesEditor()); |
120
|
|
|
|
121
|
|
|
$this->outputListEditor(); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Returns an array of module data |
126
|
|
|
* @param string $module_id |
127
|
|
|
* @return array |
128
|
|
|
*/ |
129
|
|
|
protected function setModuleEditor($module_id) |
130
|
|
|
{ |
131
|
|
|
$module = $this->module->get($module_id); |
132
|
|
|
|
133
|
|
|
if (empty($module)) { |
134
|
|
|
$this->outputHttpStatus(404); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
if ($module['type'] !== 'theme') { |
138
|
|
|
$this->outputHttpStatus(403); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
return $this->data_module = $module; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Returns an array of files to edit |
146
|
|
|
* @return array |
147
|
|
|
*/ |
148
|
|
|
protected function getFilesEditor() |
149
|
|
|
{ |
150
|
|
|
$data = $this->editor->getList($this->data_module); |
151
|
|
|
return $this->prepareFilesEditor($data); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Prepares an array of files to be edited |
156
|
|
|
* @param array $data |
157
|
|
|
* @return array |
158
|
|
|
*/ |
159
|
|
|
protected function prepareFilesEditor(array $data) |
160
|
|
|
{ |
161
|
|
|
$prepared = array(); |
162
|
|
|
foreach ($data as $folder => $files) { |
163
|
|
|
foreach ($files as $file) { |
164
|
|
|
|
165
|
|
|
$path = trim(str_replace($this->data_module['directory'], '', $file), '/'); |
166
|
|
|
$depth = substr_count($path, '/'); |
167
|
|
|
|
168
|
|
|
$pathinfo = pathinfo($path); |
169
|
|
|
|
170
|
|
|
$directory = is_dir($file); |
171
|
|
|
$parent = $directory ? $path : $pathinfo['dirname']; |
172
|
|
|
|
173
|
|
|
$prepared[$folder][$parent][] = array( |
174
|
|
|
'file' => $file, |
175
|
|
|
'path' => $path, |
176
|
|
|
'depth' => $depth, |
177
|
|
|
'directory' => $directory, |
178
|
|
|
'name' => $pathinfo['basename'], |
179
|
|
|
'id' => gplcart_string_encode($path), |
180
|
|
|
'indentation' => str_repeat('<span class="indentation"></span>', $depth) |
181
|
|
|
); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
ksort($prepared[$folder]); |
185
|
|
|
} |
186
|
|
|
return $prepared; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Sets title on theme files overview page |
191
|
|
|
*/ |
192
|
|
|
protected function setTitleListEditor() |
193
|
|
|
{ |
194
|
|
|
$text = $this->text('Edit theme %name', array('%name' => $this->data_module['name'])); |
195
|
|
|
$this->setTitle($text); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Sets breadcrumbs on theme files overview page |
200
|
|
|
*/ |
201
|
|
|
protected function setBreadcrumbListEditor() |
202
|
|
|
{ |
203
|
|
|
$breadcrumbs = array(); |
204
|
|
|
|
205
|
|
|
$breadcrumbs[] = array( |
206
|
|
|
'url' => $this->url('admin'), |
207
|
|
|
'text' => $this->text('Dashboard') |
208
|
|
|
); |
209
|
|
|
|
210
|
|
|
$breadcrumbs[] = array( |
211
|
|
|
'url' => $this->url('admin/module/list'), |
212
|
|
|
'text' => $this->text('Modules') |
213
|
|
|
); |
214
|
|
|
|
215
|
|
|
$breadcrumbs[] = array( |
216
|
|
|
'url' => $this->url('admin/tool/editor'), |
217
|
|
|
'text' => $this->text('Themes') |
218
|
|
|
); |
219
|
|
|
|
220
|
|
|
$this->setBreadcrumbs($breadcrumbs); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Renders templates of theme files overview page |
225
|
|
|
*/ |
226
|
|
|
protected function outputListEditor() |
227
|
|
|
{ |
228
|
|
|
$this->output('editor|list'); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Displays the file edit page |
233
|
|
|
* @param string $module_id |
234
|
|
|
* @param string $file_id |
235
|
|
|
*/ |
236
|
|
|
public function editEditor($module_id, $file_id) |
237
|
|
|
{ |
238
|
|
|
$this->setModuleEditor($module_id); |
239
|
|
|
$this->setFilePathEditor($file_id); |
240
|
|
|
|
241
|
|
|
$this->setTitleEditEditor(); |
242
|
|
|
$this->setBreadcrumbEditEditor(); |
243
|
|
|
|
244
|
|
|
$this->setMessageEditEditor(); |
245
|
|
|
|
246
|
|
|
$this->setData('module', $this->data_module); |
247
|
|
|
$this->setData('can_save', $this->canSaveEditor()); |
248
|
|
|
$this->setData('lines', $this->getFileTotalLinesEditor()); |
249
|
|
|
$this->setData('editor.content', $this->getFileContentEditor()); |
250
|
|
|
|
251
|
|
|
$this->submitEditor(); |
252
|
|
|
|
253
|
|
|
$this->setJsSettingsEditor(); |
254
|
|
|
$this->outputEditEditor(); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Sets messages on the file edit page |
259
|
|
|
*/ |
260
|
|
|
protected function setMessageEditEditor() |
261
|
|
|
{ |
262
|
|
|
if ($this->canSaveEditor()) { |
263
|
|
|
$message = $this->text('Before saving changes make sure you have a <a href="@url">backup</a> of the current version', array('@url' => $this->url('admin/tool/backup'))); |
264
|
|
|
$this->setMessage($message, 'warning'); |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
if ($this->current_theme['id'] == $this->data_module['id']) { |
268
|
|
|
$message = $this->text('You cannot edit the current theme'); |
269
|
|
|
$this->setMessage($message, 'warning'); |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Sets JavaScript settings on the file edit page |
275
|
|
|
*/ |
276
|
|
|
protected function setJsSettingsEditor() |
277
|
|
|
{ |
278
|
|
|
$settings = array( |
279
|
|
|
'readonly' => !$this->canSaveEditor(), |
280
|
|
|
'file_extension' => pathinfo($this->data_file, PATHINFO_EXTENSION) |
281
|
|
|
); |
282
|
|
|
|
283
|
|
|
$this->setJsSettings('editor', $settings); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* Saves an array of submitted data |
288
|
|
|
*/ |
289
|
|
|
protected function submitEditor() |
290
|
|
|
{ |
291
|
|
|
if ($this->isPosted('save') && $this->validateEditor()) { |
292
|
|
|
$this->saveEditor(); |
293
|
|
|
} |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Validates a submitted data when editing a theme file |
298
|
|
|
* @return bool |
299
|
|
|
*/ |
300
|
|
|
protected function validateEditor() |
301
|
|
|
{ |
302
|
|
|
$this->setSubmitted('editor', null, false); |
303
|
|
|
|
304
|
|
|
$this->setSubmitted('user_id', $this->uid); |
305
|
|
|
$this->setSubmitted('path', $this->data_file); |
306
|
|
|
$this->setSubmitted('module', $this->data_module); |
307
|
|
|
|
308
|
|
|
$content = $this->getSubmitted('content'); |
309
|
|
|
|
310
|
|
|
if (!empty($content)) { |
311
|
|
|
$this->validateTwigEditor($content); |
312
|
|
|
} |
313
|
|
|
return !$this->hasErrors(); |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Validates TWIG code |
318
|
|
|
* @param string $content |
319
|
|
|
* @return boolean |
320
|
|
|
*/ |
321
|
|
|
protected function validateTwigEditor($content) |
322
|
|
|
{ |
323
|
|
|
$info = pathinfo($this->data_file); |
324
|
|
|
|
325
|
|
|
if ($info['extension'] !== 'twig' || !$this->config->isEnabledModule('twig')) { |
326
|
|
|
return null; |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/* @var $module \gplcart\modules\twig\Twig */ |
330
|
|
|
$module = $this->config->getModuleInstance('twig'); |
331
|
|
|
$twig = $module->getTwigInstance($info['dirname'], $this); |
332
|
|
|
|
333
|
|
|
try { |
334
|
|
|
$twig->parse($twig->tokenize(new \Twig_Source($content, $info['basename']))); |
335
|
|
|
return true; |
336
|
|
|
} catch (\Twig_Error_Syntax $e) { |
|
|
|
|
337
|
|
|
$this->setError('content', $e->getMessage()); |
338
|
|
|
} |
339
|
|
|
return false; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* Writes a submitted content to a theme file |
344
|
|
|
*/ |
345
|
|
|
protected function saveEditor() |
346
|
|
|
{ |
347
|
|
|
$this->controlAccessSaveEditor(); |
348
|
|
|
|
349
|
|
|
$submitted = $this->getSubmitted(); |
350
|
|
|
$result = $this->editor->save($submitted); |
351
|
|
|
|
352
|
|
|
if ($result === true) { |
353
|
|
|
$message = $this->text('Theme file has been saved'); |
354
|
|
|
$this->redirect("admin/tool/editor/{$submitted['module']['id']}", $message, 'success'); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
$message = $this->text('An error occurred'); |
358
|
|
|
$this->redirect('', $message, 'warning'); |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Whether the current user can save the file |
363
|
|
|
*/ |
364
|
|
|
protected function canSaveEditor() |
365
|
|
|
{ |
366
|
|
|
return $this->access('editor_edit')// |
367
|
|
|
&& $this->current_theme['id'] != $this->data_module['id']; |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* Controls permissions to save a theme file for the current user |
372
|
|
|
*/ |
373
|
|
|
protected function controlAccessSaveEditor() |
374
|
|
|
{ |
375
|
|
|
if (!$this->canSaveEditor()) { |
376
|
|
|
$this->outputHttpStatus(403); |
377
|
|
|
} |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* Sets titles on the file edit page |
382
|
|
|
*/ |
383
|
|
|
protected function setTitleEditEditor() |
384
|
|
|
{ |
385
|
|
|
$vars = array('%name' => substr($this->data_file, strlen(GC_MODULE_DIR . '/'))); |
386
|
|
|
$text = $this->text('Edit file %name', $vars); |
387
|
|
|
$this->setTitle($text); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
/** |
391
|
|
|
* Sets breadcrumbs on the file edit page |
392
|
|
|
*/ |
393
|
|
|
protected function setBreadcrumbEditEditor() |
394
|
|
|
{ |
395
|
|
|
$breadcrumbs = array(); |
396
|
|
|
|
397
|
|
|
$breadcrumbs[] = array( |
398
|
|
|
'url' => $this->url('admin'), |
399
|
|
|
'text' => $this->text('Dashboard') |
400
|
|
|
); |
401
|
|
|
|
402
|
|
|
$breadcrumbs[] = array( |
403
|
|
|
'url' => $this->url('admin/module/list'), |
404
|
|
|
'text' => $this->text('Modules') |
405
|
|
|
); |
406
|
|
|
|
407
|
|
|
$breadcrumbs[] = array( |
408
|
|
|
'url' => $this->url("admin/tool/editor/{$this->data_module['id']}"), |
409
|
|
|
'text' => $this->text('Edit theme %name', array('%name' => $this->data_module['name'])) |
410
|
|
|
); |
411
|
|
|
|
412
|
|
|
$this->setBreadcrumbs($breadcrumbs); |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
/** |
416
|
|
|
* Renders the file edit page |
417
|
|
|
*/ |
418
|
|
|
protected function outputEditEditor() |
419
|
|
|
{ |
420
|
|
|
$this->output('editor|edit'); |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
/** |
424
|
|
|
* Returns a path to the file to be edited |
425
|
|
|
* @param string $encoded_filename URL encoded base64 hash |
426
|
|
|
* @return string |
427
|
|
|
*/ |
428
|
|
|
protected function setFilePathEditor($encoded_filename) |
429
|
|
|
{ |
430
|
|
|
$filepath = gplcart_string_decode($encoded_filename); |
431
|
|
|
$file = "{$this->data_module['directory']}/$filepath"; |
432
|
|
|
|
433
|
|
|
if (!is_file($file) || !is_readable($file)) { |
434
|
|
|
$this->outputHttpStatus(404); |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
return $this->data_file = $file; |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
/** |
441
|
|
|
* Returns a content of the file |
442
|
|
|
* @return string |
443
|
|
|
*/ |
444
|
|
|
protected function getFileContentEditor() |
445
|
|
|
{ |
446
|
|
|
return file_get_contents($this->data_file); |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* Returns the total number of lines in the file |
451
|
|
|
* @return integer |
452
|
|
|
*/ |
453
|
|
|
protected function getFileTotalLinesEditor() |
454
|
|
|
{ |
455
|
|
|
return count(file($this->data_file)); |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
} |
459
|
|
|
|
Scrutinizer analyzes your
composer.json
/composer.lock
file if available to determine the classes, and functions that are defined by your dependencies.It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.