1: | <?php
2: | namespace Opencart\Admin\Controller\Marketplace;
3: | /**
4: | * Class Modification
5: | *
6: | * @package Opencart\Admin\Controller\Marketplace
7: | */
8: | class Modification extends \Opencart\System\Engine\Controller {
9: | /**
10: | * Index
11: | *
12: | * @return void
13: | */
14: | public function index(): void {
15: | $this->load->language('marketplace/modification');
16: |
17: | $this->document->setTitle($this->language->get('heading_title'));
18: |
19: | $url = '';
20: |
21: | if (isset($this->request->get['sort'])) {
22: | $url .= '&sort=' . $this->request->get['sort'];
23: | }
24: |
25: | if (isset($this->request->get['order'])) {
26: | $url .= '&order=' . $this->request->get['order'];
27: | }
28: |
29: | if (isset($this->request->get['page'])) {
30: | $url .= '&page=' . $this->request->get['page'];
31: | }
32: |
33: | $data['breadcrumbs'] = [];
34: |
35: | $data['breadcrumbs'][] = [
36: | 'text' => $this->language->get('text_home'),
37: | 'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'])
38: | ];
39: |
40: | $data['breadcrumbs'][] = [
41: | 'text' => $this->language->get('heading_title'),
42: | 'href' => $this->url->link('marketplace/modification', 'user_token=' . $this->session->data['user_token'] . $url)
43: | ];
44: |
45: | $data['delete'] = $this->url->link('marketplace/modification.delete', 'user_token=' . $this->session->data['user_token']);
46: | $data['download'] = $this->url->link('tool/log.download', 'user_token=' . $this->session->data['user_token'] . '&filename=ocmod.log');
47: | $data['upload'] = $this->url->link('tool/installer.upload', 'user_token=' . $this->session->data['user_token']);
48: |
49: | $data['list'] = $this->getList();
50: |
51: | // Log
52: | $data['log'] = $this->getLog();
53: |
54: | $data['user_token'] = $this->session->data['user_token'];
55: |
56: | $data['header'] = $this->load->controller('common/header');
57: | $data['column_left'] = $this->load->controller('common/column_left');
58: | $data['footer'] = $this->load->controller('common/footer');
59: |
60: | $this->response->setOutput($this->load->view('marketplace/modification', $data));
61: | }
62: |
63: | /**
64: | * List
65: | *
66: | * @return void
67: | */
68: | public function list(): void {
69: | $this->load->language('marketplace/modification');
70: |
71: | $this->response->setOutput($this->getList());
72: | }
73: |
74: | /**
75: | * Get List
76: | *
77: | * @return string
78: | */
79: | public function getList(): string {
80: | if (isset($this->request->get['sort'])) {
81: | $sort = (string)$this->request->get['sort'];
82: | } else {
83: | $sort = 'name';
84: | }
85: |
86: | if (isset($this->request->get['order'])) {
87: | $order = (string)$this->request->get['order'];
88: | } else {
89: | $order = 'ASC';
90: | }
91: |
92: | if (isset($this->request->get['page'])) {
93: | $page = (int)$this->request->get['page'];
94: | } else {
95: | $page = 1;
96: | }
97: |
98: | $url = '';
99: |
100: | if (isset($this->request->get['sort'])) {
101: | $url .= '&sort=' . $this->request->get['sort'];
102: | }
103: |
104: | if (isset($this->request->get['order'])) {
105: | $url .= '&order=' . $this->request->get['order'];
106: | }
107: |
108: | if (isset($this->request->get['page'])) {
109: | $url .= '&page=' . $this->request->get['page'];
110: | }
111: |
112: | $data['action'] = $this->url->link('marketplace/modification.list', 'user_token=' . $this->session->data['user_token'] . $url);
113: |
114: | $data['modifications'] = [];
115: |
116: | $filter_data = [
117: | 'sort' => $sort,
118: | 'order' => $order,
119: | 'start' => ($page - 1) * $this->config->get('config_pagination_admin'),
120: | 'limit' => $this->config->get('config_pagination_admin')
121: | ];
122: |
123: | $this->load->model('setting/modification');
124: |
125: | $results = $this->model_setting_modification->getModifications($filter_data);
126: |
127: | foreach ($results as $result) {
128: | $data['modifications'][] = [
129: | 'modification_id' => $result['modification_id'],
130: | 'name' => $result['name'],
131: | 'code' => $result['code'],
132: | 'description' => $result['description'],
133: | 'author' => $result['author'],
134: | 'version' => $result['version'],
135: | 'xml' => $result['xml'],
136: | 'status' => $result['status'],
137: | 'date_added' => date($this->language->get('date_format_short'), strtotime($result['date_added'])),
138: | 'link' => $result['link'],
139: | 'enable' => $this->url->link('marketplace/modification.enable', 'user_token=' . $this->session->data['user_token'] . '&modification_id=' . $result['modification_id']),
140: | 'disable' => $this->url->link('marketplace/modification.disable', 'user_token=' . $this->session->data['user_token'] . '&modification_id=' . $result['modification_id'])
141: | ];
142: | }
143: |
144: | $url = '';
145: |
146: | if ($order == 'ASC') {
147: | $url .= '&order=DESC';
148: | } else {
149: | $url .= '&order=ASC';
150: | }
151: |
152: | $data['sort_name'] = $this->url->link('marketplace/modification', 'user_token=' . $this->session->data['user_token'] . '&sort=name' . $url, true);
153: | $data['sort_author'] = $this->url->link('marketplace/modification', 'user_token=' . $this->session->data['user_token'] . '&sort=author' . $url, true);
154: | $data['sort_version'] = $this->url->link('marketplace/modification', 'user_token=' . $this->session->data['user_token'] . '&sort=version' . $url, true);
155: | $data['sort_date_added'] = $this->url->link('marketplace/modification', 'user_token=' . $this->session->data['user_token'] . '&sort=date_added' . $url, true);
156: |
157: | $url = '';
158: |
159: | if (isset($this->request->get['sort'])) {
160: | $url .= '&sort=' . $this->request->get['sort'];
161: | }
162: |
163: | if (isset($this->request->get['order'])) {
164: | $url .= '&order=' . $this->request->get['order'];
165: | }
166: |
167: | $modification_total = $this->model_setting_modification->getTotalModifications();
168: |
169: | $data['pagination'] = $this->load->controller('common/pagination', [
170: | 'total' => $modification_total,
171: | 'page' => $page,
172: | 'limit' => $this->config->get('config_pagination_admin'),
173: | 'url' => $this->url->link('marketplace/modification.list', 'user_token=' . $this->session->data['user_token'] . $url . '&page={page}')
174: | ]);
175: |
176: | $data['results'] = sprintf($this->language->get('text_pagination'), ($modification_total) ? (($page - 1) * $this->config->get('config_pagination_admin')) + 1 : 0, ((($page - 1) * $this->config->get('config_pagination_admin')) > ($modification_total - $this->config->get('config_pagination_admin'))) ? $modification_total : ((($page - 1) * $this->config->get('config_pagination_admin')) + $this->config->get('config_pagination_admin')), $modification_total, ceil($modification_total / $this->config->get('config_pagination_admin')));
177: |
178: | $data['sort'] = $sort;
179: | $data['order'] = $order;
180: |
181: | return $this->load->view('marketplace/modification_list', $data);
182: | }
183: |
184: | /**
185: | * Refresh
186: | *
187: | * @throws \Exception
188: | *
189: | * @return void
190: | */
191: | public function refresh(): void {
192: | $this->load->language('marketplace/modification');
193: |
194: | $json = [];
195: |
196: | if (!$this->user->hasPermission('modify', 'marketplace/modification')) {
197: | $json['error'] = $this->language->get('error_permission');
198: | }
199: |
200: | if (!$json) {
201: | // Just before files are deleted, if config settings say maintenance mode is off then turn it on
202: | $maintenance = $this->config->get('config_maintenance');
203: |
204: | $this->load->model('setting/setting');
205: |
206: | $this->model_setting_setting->editValue('config', 'config_maintenance', '1');
207: |
208: | // Clear all modification files
209: | $files = [];
210: |
211: | // Make path into an array
212: | $path = [DIR_EXTENSION . 'ocmod/*'];
213: |
214: | // While the path array is still populated keep looping through
215: | while (count($path) != 0) {
216: | $next = array_shift($path);
217: |
218: | foreach (glob($next) as $file) {
219: | // If directory add to path array
220: | if (is_dir($file)) {
221: | $path[] = $file . '/*';
222: | }
223: |
224: | // Add the file to the files to be deleted array
225: | $files[] = $file;
226: | }
227: | }
228: |
229: | // Reverse sort the file array
230: | rsort($files);
231: |
232: | // Clear all modification files
233: | foreach ($files as $file) {
234: | if ($file != DIR_EXTENSION . 'ocmod/index.html') {
235: | // If file just delete
236: | if (is_file($file)) {
237: | unlink($file);
238: |
239: | // If directory use the remove directory function
240: | } elseif (is_dir($file)) {
241: | rmdir($file);
242: | }
243: | }
244: | }
245: |
246: | // Begin
247: | $xml = [];
248: |
249: | // This is purely so developers they can run mods directly and have them run without upload after each change.
250: | $files = glob(DIR_SYSTEM . '*.ocmod.xml');
251: |
252: | if ($files) {
253: | foreach ($files as $file) {
254: | $xml[] = file_get_contents($file);
255: | }
256: | }
257: |
258: | $this->load->model('setting/modification');
259: |
260: | $results = $this->model_setting_modification->getModifications();
261: |
262: | foreach ($results as $result) {
263: | if ($result['status']) {
264: | $xml[] = $result['xml'];
265: | }
266: | }
267: |
268: | // Log
269: | $log = [];
270: |
271: | $original = [];
272: | $modification = [];
273: |
274: | foreach ($xml as $xml) {
275: | if (empty($xml)) {
276: | continue;
277: | }
278: |
279: | $dom = new \DOMDocument('1.0', 'UTF-8');
280: | $dom->preserveWhiteSpace = false;
281: | $dom->loadXml($xml);
282: |
283: | // Log
284: | $log[] = 'MOD: ' . $dom->getElementsByTagName('name')->item(0)->textContent;
285: |
286: | // Store a backup recovery of the modification code in case we need to use it if an abort attribute is used.
287: | $recovery = $modification;
288: |
289: | $files = $dom->getElementsByTagName('modification')->item(0)->getElementsByTagName('file');
290: |
291: | foreach ($files as $file) {
292: | $operations = $file->getElementsByTagName('operation');
293: |
294: | $files = explode('|', str_replace("\\", '/', $file->getAttribute('path')));
295: |
296: | foreach ($files as $file) {
297: | $path = '';
298: |
299: | // Get the full path of the files that are going to be used for modification
300: | if ((substr($file, 0, 7) == 'catalog')) {
301: | $path = DIR_CATALOG . substr($file, 8);
302: | }
303: |
304: | if ((substr($file, 0, 5) == 'admin')) {
305: | $path = DIR_APPLICATION . substr($file, 6);
306: | }
307: |
308: | if ((substr($file, 0, 9) == 'extension')) {
309: | $path = DIR_EXTENSION . substr($file, 9);
310: | }
311: |
312: | if ((substr($file, 0, 6) == 'system')) {
313: | $path = DIR_SYSTEM . substr($file, 7);
314: | }
315: |
316: | if ($path) {
317: | $files = glob($path, GLOB_BRACE);
318: |
319: | if ($files) {
320: | foreach ($files as $file) {
321: | if (substr($file, 0, strlen(DIR_APPLICATION)) == DIR_APPLICATION) {
322: | $key = 'admin/' . substr($file, strlen(DIR_APPLICATION));
323: | }
324: |
325: | // Get the key to be used for the modification cache filename.
326: | if (substr($file, 0, strlen(DIR_CATALOG)) == DIR_CATALOG) {
327: | $key = 'catalog/' . substr($file, strlen(DIR_CATALOG));
328: | }
329: |
330: | if (substr($file, 0, strlen(DIR_EXTENSION)) == DIR_EXTENSION) {
331: | $key = 'extension/' . substr($file, strlen(DIR_EXTENSION));
332: | }
333: |
334: | if (substr($file, 0, strlen(DIR_SYSTEM)) == DIR_SYSTEM) {
335: | $key = 'system/' . substr($file, strlen(DIR_SYSTEM));
336: | }
337: |
338: | // If file contents is not already in the modification array we need to load it.
339: | if (!isset($modification[$key])) {
340: | $content = file_get_contents($file);
341: |
342: | $modification[$key] = preg_replace('~\r?\n~', "\n", $content);
343: | $original[$key] = preg_replace('~\r?\n~', "\n", $content);
344: |
345: | // Log
346: | $log[] = PHP_EOL . 'FILE: ' . $key;
347: |
348: | } else {
349: | // Log
350: | $log[] = PHP_EOL . 'FILE: (sub modification) ' . $key;
351: |
352: | }
353: |
354: | foreach ($operations as $operation) {
355: | $error = $operation->getAttribute('error');
356: |
357: | // Ignoreif
358: | $ignoreif = $operation->getElementsByTagName('ignoreif')->item(0);
359: |
360: | if ($ignoreif) {
361: | if ($ignoreif->getAttribute('regex') != 'true') {
362: | if (strpos($modification[$key], $ignoreif->textContent) !== false) {
363: | continue;
364: | }
365: | } else {
366: | if (preg_match($ignoreif->textContent, $modification[$key])) {
367: | continue;
368: | }
369: | }
370: | }
371: |
372: | $status = false;
373: |
374: | // Search and replace
375: | if ($operation->getElementsByTagName('search')->item(0)->getAttribute('regex') != 'true') {
376: | // Search
377: | $search = $operation->getElementsByTagName('search')->item(0)->textContent;
378: | $trim = $operation->getElementsByTagName('search')->item(0)->getAttribute('trim');
379: | $index = $operation->getElementsByTagName('search')->item(0)->getAttribute('index');
380: |
381: | // Trim line if no trim attribute is set or is set to true.
382: | if (!$trim || $trim == 'true') {
383: | $search = trim($search);
384: | }
385: |
386: | // Add
387: | $add = $operation->getElementsByTagName('add')->item(0)->textContent;
388: | $trim = $operation->getElementsByTagName('add')->item(0)->getAttribute('trim');
389: | $position = $operation->getElementsByTagName('add')->item(0)->getAttribute('position');
390: | $offset = (int)$operation->getElementsByTagName('add')->item(0)->getAttribute('offset');
391: |
392: | // Trim line if is set to true.
393: | if ($trim == 'true') {
394: | $add = trim($add);
395: | }
396: |
397: | // Log
398: | $log[] = 'CODE: ' . $search;
399: |
400: | // Check if using indexes
401: | if ($index !== '') {
402: | $indexes = explode(',', $index);
403: | } else {
404: | $indexes = '';
405: | }
406: |
407: | // Get all the matches
408: | $i = 0;
409: |
410: | $lines = explode("\n", $modification[$key]);
411: |
412: | for ($line_id = 0; $line_id < count($lines); $line_id++) {
413: | $line = $lines[$line_id];
414: |
415: | // Status
416: | $match = false;
417: |
418: | // Check to see if the line matches the search code.
419: | if (stripos($line, $search) !== false) {
420: | // If indexes are not used then just set the found status to true.
421: | if (!$indexes) {
422: | $match = true;
423: | } elseif (in_array($i, $indexes)) {
424: | $match = true;
425: | }
426: |
427: | $i++;
428: | }
429: |
430: | // Now for replacing or adding to the matched elements
431: | if ($match) {
432: | switch ($position) {
433: | default:
434: | case 'replace':
435: | $new_lines = explode("\n", $add);
436: |
437: | if ($offset < 0) {
438: | array_splice($lines, $line_id + $offset, abs($offset) + 1, [str_replace($search, $add, $line)]);
439: |
440: | $line_id -= $offset;
441: | } else {
442: | array_splice($lines, $line_id, $offset + 1, [str_replace($search, $add, $line)]);
443: | }
444: | break;
445: | case 'before':
446: | $new_lines = explode("\n", $add);
447: |
448: | array_splice($lines, $line_id - $offset, 0, $new_lines);
449: |
450: | $line_id += count($new_lines);
451: | break;
452: | case 'after':
453: | $new_lines = explode("\n", $add);
454: |
455: | array_splice($lines, ($line_id + 1) + $offset, 0, $new_lines);
456: |
457: | $line_id += count($new_lines);
458: | break;
459: | }
460: |
461: | // Log
462: | $log[] = 'LINE: ' . $line_id;
463: |
464: | $status = true;
465: | }
466: | }
467: |
468: | $modification[$key] = implode("\n", $lines);
469: | } else {
470: | $search = trim($operation->getElementsByTagName('search')->item(0)->textContent);
471: | $limit = (int)$operation->getElementsByTagName('search')->item(0)->getAttribute('limit');
472: | $replace = trim($operation->getElementsByTagName('add')->item(0)->textContent);
473: |
474: | // Limit
475: | if (!$limit) {
476: | $limit = -1;
477: | }
478: |
479: | // Log
480: | $match = [];
481: |
482: | preg_match_all($search, $modification[$key], $match, PREG_OFFSET_CAPTURE);
483: |
484: | // Remove part of the result if a limit is set.
485: | if ($limit > 0) {
486: | $match[0] = array_slice($match[0], 0, $limit);
487: | }
488: |
489: | if ($match[0]) {
490: | $log[] = 'REGEX: ' . $search;
491: |
492: | for ($i = 0; $i < count($match[0]); $i++) {
493: | $log[] = 'LINE: ' . (substr_count(substr($modification[$key], 0, $match[0][$i][1]), "\n") + 1);
494: | }
495: |
496: | $status = true;
497: | }
498: |
499: | // Make the modification
500: | $modification[$key] = preg_replace($search, $replace, $modification[$key], $limit);
501: | }
502: |
503: | if (!$status) {
504: | // Abort applying this modification completely.
505: | if ($error == 'abort') {
506: | $modification = $recovery;
507: | // Log
508: | $log[] = 'NOT FOUND - ABORTING!';
509: | break 4;
510: | }
511: | // Skip current operation or break
512: | elseif ($error == 'skip') {
513: | // Log
514: | $log[] = 'NOT FOUND - OPERATION SKIPPED!';
515: | continue;
516: | }
517: | // Break current operations
518: | else {
519: | // Log
520: | $log[] = 'NOT FOUND - OPERATIONS ABORTED!';
521: | break;
522: | }
523: | }
524: | }
525: | }
526: | }
527: | }
528: | }
529: | }
530: |
531: | // Log
532: | $log[] = '----------------------------------------------------------------';
533: | }
534: |
535: | // Log
536: | $ocmod = new \Opencart\System\Library\Log('ocmod.log');
537: | $ocmod->write(implode("\n", $log));
538: |
539: | // Write all modification files
540: | foreach ($modification as $key => $value) {
541: | // Only create a file if there are changes
542: | if ($original[$key] != $value) {
543: | $path = '';
544: |
545: | $directories = explode('/', dirname($key));
546: |
547: | foreach ($directories as $directory) {
548: | $path = $path . '/' . $directory;
549: |
550: | if (!is_dir(DIR_EXTENSION . 'ocmod/' . $path)) {
551: | @mkdir(DIR_EXTENSION . 'ocmod/' . $path, 0777);
552: | }
553: | }
554: |
555: | $handle = fopen(DIR_EXTENSION . 'ocmod/' . $key, 'w');
556: |
557: | fwrite($handle, $value);
558: |
559: | fclose($handle);
560: | }
561: | }
562: |
563: | // Maintance mode back to original settings
564: | $this->model_setting_setting->editValue('config', 'config_maintenance', $maintenance);
565: |
566: | // Do not return success message if refresh() was called with $data
567: | $json['success'] = $this->language->get('text_success');
568: | }
569: |
570: | $this->response->addHeader('Content-Type: application/json');
571: | $this->response->setOutput(json_encode($json));
572: | }
573: |
574: | /**
575: | * Log
576: | *
577: | * @return void
578: | */
579: | public function log(): void {
580: | $this->response->setOutput($this->getLog());
581: | }
582: |
583: | /**
584: | * getLog
585: | *
586: | * @return string
587: | */
588: | public function getLog(): string {
589: | $file = DIR_LOGS . 'ocmod.log';
590: |
591: | if (is_file($file)) {
592: | return htmlentities(file_get_contents($file, true, null));
593: | } else {
594: | return '';
595: | }
596: | }
597: |
598: | /**
599: | * Clear
600: | *
601: | * @return void
602: | */
603: | public function clear(): void {
604: | $this->load->language('marketplace/modification');
605: |
606: | $json = [];
607: |
608: | if (!$this->user->hasPermission('modify', 'marketplace/modification')) {
609: | $json['error'] = $this->language->get('error_permission');
610: | }
611: |
612: | if (!$json) {
613: | $files = [];
614: |
615: | // Make path into an array
616: | $path = [DIR_EXTENSION . 'ocmod/*'];
617: |
618: | // While the path array is still populated keep looping through
619: | while (count($path) != 0) {
620: | $next = array_shift($path);
621: |
622: | foreach (glob($next) as $file) {
623: | // If directory add to path array
624: | if (is_dir($file)) {
625: | $path[] = $file . '/*';
626: | }
627: |
628: | // Add the file to the files to be deleted array
629: | $files[] = $file;
630: | }
631: | }
632: |
633: | // Reverse sort the file array
634: | rsort($files);
635: |
636: | // Clear all modification files
637: | foreach ($files as $file) {
638: | if ($file != DIR_EXTENSION . 'ocmod/index.html') {
639: | // If file just delete
640: | if (is_file($file)) {
641: | unlink($file);
642: |
643: | // If directory use the remove directory function
644: | } elseif (is_dir($file)) {
645: | rmdir($file);
646: | }
647: | }
648: | }
649: |
650: | $json['success'] = $this->language->get('text_success');
651: | }
652: |
653: | $this->response->addHeader('Content-Type: application/json');
654: | $this->response->setOutput(json_encode($json));
655: | }
656: |
657: | /**
658: | * Enable
659: | *
660: | * @return void
661: | */
662: | public function enable(): void {
663: | $this->load->language('marketplace/modification');
664: |
665: | $json = [];
666: |
667: | if (isset($this->request->get['modification_id'])) {
668: | $modification_id = (int)$this->request->get['modification_id'];
669: | } else {
670: | $modification_id = 0;
671: | }
672: |
673: | if (!$this->user->hasPermission('modify', 'marketplace/modification')) {
674: | $json['error'] = $this->language->get('error_permission');
675: | }
676: |
677: | if (!$json) {
678: | $this->load->model('setting/modification');
679: |
680: | $this->model_setting_modification->editStatus($modification_id, true);
681: |
682: | $json['success'] = $this->language->get('text_success');
683: | }
684: |
685: | $this->response->addHeader('Content-Type: application/json');
686: | $this->response->setOutput(json_encode($json));
687: | }
688: |
689: | /**
690: | * Disable
691: | *
692: | * @return void
693: | */
694: | public function disable(): void {
695: | $this->load->language('marketplace/modification');
696: |
697: | $json = [];
698: |
699: | if (isset($this->request->get['modification_id'])) {
700: | $modification_id = (int)$this->request->get['modification_id'];
701: | } else {
702: | $modification_id = 0;
703: | }
704: |
705: | if (!$this->user->hasPermission('modify', 'marketplace/modification')) {
706: | $json['error'] = $this->language->get('error_permission');
707: | }
708: |
709: | if (!$json) {
710: | $this->load->model('setting/modification');
711: |
712: | $this->model_setting_modification->editStatus($modification_id, false);
713: |
714: | $json['success'] = $this->language->get('text_success');
715: | }
716: |
717: | $this->response->addHeader('Content-Type: application/json');
718: | $this->response->setOutput(json_encode($json));
719: | }
720: |
721: | /**
722: | * Delete
723: | *
724: | * @return void
725: | */
726: | public function delete(): void {
727: | $this->load->language('marketplace/modification');
728: |
729: | $json = [];
730: |
731: | if (isset($this->request->post['selected'])) {
732: | $selected = (array)$this->request->post['selected'];
733: | } else {
734: | $selected = [];
735: | }
736: |
737: | if (!$this->user->hasPermission('modify', 'marketplace/modification')) {
738: | $json['error'] = $this->language->get('error_permission');
739: | }
740: |
741: | if (!$json) {
742: | $this->load->model('setting/modification');
743: |
744: | foreach ($selected as $modification_id) {
745: | $this->model_setting_modification->deleteModification($modification_id);
746: | }
747: |
748: | $json['success'] = $this->language->get('text_success');
749: | }
750: |
751: | $this->response->addHeader('Content-Type: application/json');
752: | $this->response->setOutput(json_encode($json));
753: | }
754: | }
755: |

OpenCart API API documentation generated by ApiGen dev-master