load->language('marketplace/modification'); $this->document->setTitle($this->language->get('heading_title')); $url = ''; if (isset($this->request->get['sort'])) { $url .= '&sort=' . $this->request->get['sort']; } if (isset($this->request->get['order'])) { $url .= '&order=' . $this->request->get['order']; } if (isset($this->request->get['page'])) { $url .= '&page=' . $this->request->get['page']; } $data['breadcrumbs'] = []; $data['breadcrumbs'][] = [ 'text' => $this->language->get('text_home'), 'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token']) ]; $data['breadcrumbs'][] = [ 'text' => $this->language->get('heading_title'), 'href' => $this->url->link('marketplace/modification', 'user_token=' . $this->session->data['user_token'] . $url) ]; $data['delete'] = $this->url->link('marketplace/modification.delete', 'user_token=' . $this->session->data['user_token']); $data['download'] = $this->url->link('tool/log.download', 'user_token=' . $this->session->data['user_token'] . '&filename=ocmod.log'); $data['upload'] = $this->url->link('tool/installer.upload', 'user_token=' . $this->session->data['user_token']); $data['list'] = $this->getList(); // Log $data['log'] = $this->getLog(); $data['user_token'] = $this->session->data['user_token']; $data['header'] = $this->load->controller('common/header'); $data['column_left'] = $this->load->controller('common/column_left'); $data['footer'] = $this->load->controller('common/footer'); $this->response->setOutput($this->load->view('marketplace/modification', $data)); } /** * List * * @return void */ public function list(): void { $this->load->language('marketplace/modification'); $this->response->setOutput($this->getList()); } /** * Get List * * @return string */ public function getList(): string { if (isset($this->request->get['sort'])) { $sort = (string)$this->request->get['sort']; } else { $sort = 'name'; } if (isset($this->request->get['order'])) { $order = (string)$this->request->get['order']; } else { $order = 'ASC'; } if (isset($this->request->get['page'])) { $page = (int)$this->request->get['page']; } else { $page = 1; } $url = ''; if (isset($this->request->get['sort'])) { $url .= '&sort=' . $this->request->get['sort']; } if (isset($this->request->get['order'])) { $url .= '&order=' . $this->request->get['order']; } if (isset($this->request->get['page'])) { $url .= '&page=' . $this->request->get['page']; } $data['action'] = $this->url->link('marketplace/modification.list', 'user_token=' . $this->session->data['user_token'] . $url); // Modification $data['modifications'] = []; $filter_data = [ 'sort' => $sort, 'order' => $order, 'start' => ($page - 1) * $this->config->get('config_pagination_admin'), 'limit' => $this->config->get('config_pagination_admin') ]; $this->load->model('setting/modification'); $results = $this->model_setting_modification->getModifications($filter_data); foreach ($results as $result) { $data['modifications'][] = [ 'date_added' => date($this->language->get('date_format_short'), strtotime($result['date_added'])), 'enable' => $this->url->link('marketplace/modification.enable', 'user_token=' . $this->session->data['user_token'] . '&modification_id=' . $result['modification_id']), 'disable' => $this->url->link('marketplace/modification.disable', 'user_token=' . $this->session->data['user_token'] . '&modification_id=' . $result['modification_id']) ] + $result; } $url = ''; if ($order == 'ASC') { $url .= '&order=DESC'; } else { $url .= '&order=ASC'; } $data['sort_name'] = $this->url->link('marketplace/modification', 'user_token=' . $this->session->data['user_token'] . '&sort=name' . $url, true); $data['sort_author'] = $this->url->link('marketplace/modification', 'user_token=' . $this->session->data['user_token'] . '&sort=author' . $url, true); $data['sort_version'] = $this->url->link('marketplace/modification', 'user_token=' . $this->session->data['user_token'] . '&sort=version' . $url, true); $data['sort_date_added'] = $this->url->link('marketplace/modification', 'user_token=' . $this->session->data['user_token'] . '&sort=date_added' . $url, true); $url = ''; if (isset($this->request->get['sort'])) { $url .= '&sort=' . $this->request->get['sort']; } if (isset($this->request->get['order'])) { $url .= '&order=' . $this->request->get['order']; } $modification_total = $this->model_setting_modification->getTotalModifications(); $data['pagination'] = $this->load->controller('common/pagination', [ 'total' => $modification_total, 'page' => $page, 'limit' => $this->config->get('config_pagination_admin'), 'url' => $this->url->link('marketplace/modification.list', 'user_token=' . $this->session->data['user_token'] . $url . '&page={page}') ]); $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'))); $data['sort'] = $sort; $data['order'] = $order; return $this->load->view('marketplace/modification_list', $data); } /** * Refresh * * @throws \Exception * * @return void */ public function refresh(): void { $this->load->language('marketplace/modification'); $json = []; if (!$this->user->hasPermission('modify', 'marketplace/modification')) { $json['error'] = $this->language->get('error_permission'); } if (!$json) { // Just before files are deleted, if config settings say maintenance mode is off then turn it on $maintenance = $this->config->get('config_maintenance'); // Setting $this->load->model('setting/setting'); $this->model_setting_setting->editValue('config', 'config_maintenance', '1'); // Clear all modification files $files = []; // Make path into an array $path = [DIR_EXTENSION . 'ocmod/*']; // While the path array is still populated keep looping through while (count($path) != 0) { $next = array_shift($path); foreach (glob($next) as $file) { // If directory add to path array if (is_dir($file)) { $path[] = $file . '/*'; } // Add the file to the files to be deleted array $files[] = $file; } } // Reverse sort the file array rsort($files); // Clear all modification files foreach ($files as $file) { if ($file != DIR_EXTENSION . 'ocmod/index.html') { // If file just delete if (is_file($file)) { unlink($file); // If directory use the remove directory function } elseif (is_dir($file)) { rmdir($file); } } } // Begin $xml = []; // This is purely so developers they can run mods directly and have them run without upload after each change. $files = glob(DIR_SYSTEM . '*.ocmod.xml'); if ($files) { foreach ($files as $file) { $xml[] = file_get_contents($file); } } $this->load->model('setting/modification'); $results = $this->model_setting_modification->getModifications(); foreach ($results as $result) { if ($result['status']) { $xml[] = $result['xml']; } } // Log $log = []; $original = []; $modification = []; foreach ($xml as $xml) { if (empty($xml)) { continue; } $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->preserveWhiteSpace = false; $dom->loadXml($xml); // Log $log[] = 'MOD: ' . $dom->getElementsByTagName('name')->item(0)->textContent; // Store a backup recovery of the modification code in case we need to use it if an abort attribute is used. $recovery = $modification; $files = $dom->getElementsByTagName('modification')->item(0)->getElementsByTagName('file'); foreach ($files as $file) { $operations = $file->getElementsByTagName('operation'); $files = explode('|', str_replace("\\", '/', $file->getAttribute('path'))); foreach ($files as $file) { $path = ''; // Get the full path of the files that are going to be used for modification if ((substr($file, 0, 7) == 'catalog')) { $path = DIR_CATALOG . substr($file, 8); } if ((substr($file, 0, 5) == 'admin')) { $path = DIR_APPLICATION . substr($file, 6); } if ((substr($file, 0, 9) == 'extension')) { $path = DIR_EXTENSION . substr($file, 10); } if ((substr($file, 0, 6) == 'system')) { $path = DIR_SYSTEM . substr($file, 7); } if ($path) { $files = glob($path, GLOB_BRACE); if ($files) { foreach ($files as $file) { if (substr($file, 0, strlen(DIR_APPLICATION)) == DIR_APPLICATION) { $key = 'admin/' . substr($file, strlen(DIR_APPLICATION)); } // Get the key to be used for the modification cache filename. if (substr($file, 0, strlen(DIR_CATALOG)) == DIR_CATALOG) { $key = 'catalog/' . substr($file, strlen(DIR_CATALOG)); } if (substr($file, 0, strlen(DIR_EXTENSION)) == DIR_EXTENSION) { $key = 'extension/' . substr($file, strlen(DIR_EXTENSION)); } if (substr($file, 0, strlen(DIR_SYSTEM)) == DIR_SYSTEM) { $key = 'system/' . substr($file, strlen(DIR_SYSTEM)); } // If file contents is not already in the modification array we need to load it. if (!isset($modification[$key])) { $content = file_get_contents($file); $modification[$key] = preg_replace('~\r?\n~', "\n", $content); $original[$key] = preg_replace('~\r?\n~', "\n", $content); // Log $log[] = PHP_EOL . 'FILE: ' . $key; } else { // Log $log[] = PHP_EOL . 'FILE: (sub modification) ' . $key; } foreach ($operations as $operation) { $error = $operation->getAttribute('error'); // Ignoreif $ignoreif = $operation->getElementsByTagName('ignoreif')->item(0); if ($ignoreif) { if ($ignoreif->getAttribute('regex') != 'true') { if (strpos($modification[$key], $ignoreif->textContent) !== false) { continue; } } else { if (preg_match($ignoreif->textContent, $modification[$key])) { continue; } } } $status = false; // Search and replace if ($operation->getElementsByTagName('search')->item(0)->getAttribute('regex') != 'true') { // Search $search = $operation->getElementsByTagName('search')->item(0)->textContent; $trim = $operation->getElementsByTagName('search')->item(0)->getAttribute('trim'); $index = $operation->getElementsByTagName('search')->item(0)->getAttribute('index'); // Trim line if no trim attribute is set or is set to true. if (!$trim || $trim == 'true') { $search = trim($search); } // Add $add = $operation->getElementsByTagName('add')->item(0)->textContent; $trim = $operation->getElementsByTagName('add')->item(0)->getAttribute('trim'); $position = $operation->getElementsByTagName('add')->item(0)->getAttribute('position'); $offset = (int)$operation->getElementsByTagName('add')->item(0)->getAttribute('offset'); // Trim line if is set to true. if ($trim == 'true') { $add = trim($add); } // Log $log[] = 'CODE: ' . $search; // Check if using indexes if ($index !== '') { $indexes = explode(',', $index); } else { $indexes = ''; } // Get all the matches $i = 0; $lines = explode("\n", $modification[$key]); for ($line_id = 0; $line_id < count($lines); $line_id++) { $line = $lines[$line_id]; // Status $match = false; // Check to see if the line matches the search code. if (stripos($line, $search) !== false) { // If indexes are not used then just set the found status to true. if (!$indexes) { $match = true; } elseif (in_array($i, $indexes)) { $match = true; } $i++; } // Now for replacing or adding to the matched elements if ($match) { switch ($position) { default: case 'replace': $new_lines = explode("\n", $add); if ($offset < 0) { array_splice($lines, $line_id + $offset, abs($offset) + 1, [str_replace($search, $add, $line)]); $line_id -= $offset; } else { array_splice($lines, $line_id, $offset + 1, [str_replace($search, $add, $line)]); } break; case 'before': $new_lines = explode("\n", $add); array_splice($lines, $line_id - $offset, 0, $new_lines); $line_id += count($new_lines); break; case 'after': $new_lines = explode("\n", $add); array_splice($lines, ($line_id + 1) + $offset, 0, $new_lines); $line_id += count($new_lines); break; } // Log $log[] = 'LINE: ' . $line_id; $status = true; } } $modification[$key] = implode("\n", $lines); } else { $search = trim($operation->getElementsByTagName('search')->item(0)->textContent); $limit = (int)$operation->getElementsByTagName('search')->item(0)->getAttribute('limit'); $replace = trim($operation->getElementsByTagName('add')->item(0)->textContent); // Limit if (!$limit) { $limit = -1; } // Log $match = []; preg_match_all($search, $modification[$key], $match, PREG_OFFSET_CAPTURE); // Remove part of the result if a limit is set. if ($limit > 0) { $match[0] = array_slice($match[0], 0, $limit); } if ($match[0]) { $log[] = 'REGEX: ' . $search; for ($i = 0; $i < count($match[0]); $i++) { $log[] = 'LINE: ' . (substr_count(substr($modification[$key], 0, $match[0][$i][1]), "\n") + 1); } $status = true; } // Make the modification $modification[$key] = preg_replace($search, $replace, $modification[$key], $limit); } if (!$status) { // Abort applying this modification completely. if ($error == 'abort') { $modification = $recovery; // Log $log[] = 'NOT FOUND - ABORTING!'; break 4; } // Skip current operation or break elseif ($error == 'skip') { // Log $log[] = 'NOT FOUND - OPERATION SKIPPED!'; continue; } // Break current operations else { // Log $log[] = 'NOT FOUND - OPERATIONS ABORTED!'; break; } } } } } } } } // Log $log[] = '----------------------------------------------------------------'; } // Log $ocmod = new \Opencart\System\Library\Log('ocmod.log'); $ocmod->write(implode("\n", $log)); // Write all modification files foreach ($modification as $key => $value) { // Only create a file if there are changes if ($original[$key] != $value) { $path = ''; $directories = explode('/', dirname($key)); foreach ($directories as $directory) { $path = $path . '/' . $directory; if (!is_dir(DIR_EXTENSION . 'ocmod/' . $path)) { @mkdir(DIR_EXTENSION . 'ocmod/' . $path, 0777); } } $handle = fopen(DIR_EXTENSION . 'ocmod/' . $key, 'w'); fwrite($handle, $value); fclose($handle); } } // Maintance mode back to original settings $this->model_setting_setting->editValue('config', 'config_maintenance', $maintenance); // Do not return success message if refresh() was called with $data $json['success'] = $this->language->get('text_success'); } $this->response->addHeader('Content-Type: application/json'); $this->response->setOutput(json_encode($json)); } /** * Log * * @return void */ public function log(): void { $this->response->setOutput($this->getLog()); } /** * getLog * * @return string */ public function getLog(): string { $file = DIR_LOGS . 'ocmod.log'; if (is_file($file)) { return htmlentities(file_get_contents($file, true, null)); } else { return ''; } } /** * Clear * * @return void */ public function clear(): void { $this->load->language('marketplace/modification'); $json = []; if (!$this->user->hasPermission('modify', 'marketplace/modification')) { $json['error'] = $this->language->get('error_permission'); } if (!$json) { $files = []; // Make path into an array $path = [DIR_EXTENSION . 'ocmod/*']; // While the path array is still populated keep looping through while (count($path) != 0) { $next = array_shift($path); foreach (glob($next) as $file) { // If directory add to path array if (is_dir($file)) { $path[] = $file . '/*'; } // Add the file to the files to be deleted array $files[] = $file; } } // Reverse sort the file array rsort($files); // Clear all modification files foreach ($files as $file) { if ($file != DIR_EXTENSION . 'ocmod/index.html') { // If file just delete if (is_file($file)) { unlink($file); // If directory use the remove directory function } elseif (is_dir($file)) { rmdir($file); } } } $json['success'] = $this->language->get('text_success'); } $this->response->addHeader('Content-Type: application/json'); $this->response->setOutput(json_encode($json)); } /** * Enable * * @return void */ public function enable(): void { $this->load->language('marketplace/modification'); $json = []; if (isset($this->request->get['modification_id'])) { $modification_id = (int)$this->request->get['modification_id']; } else { $modification_id = 0; } if (!$this->user->hasPermission('modify', 'marketplace/modification')) { $json['error'] = $this->language->get('error_permission'); } if (!$json) { $this->load->model('setting/modification'); $this->model_setting_modification->editStatus($modification_id, true); $json['success'] = $this->language->get('text_success'); } $this->response->addHeader('Content-Type: application/json'); $this->response->setOutput(json_encode($json)); } /** * Disable * * @return void */ public function disable(): void { $this->load->language('marketplace/modification'); $json = []; if (isset($this->request->get['modification_id'])) { $modification_id = (int)$this->request->get['modification_id']; } else { $modification_id = 0; } if (!$this->user->hasPermission('modify', 'marketplace/modification')) { $json['error'] = $this->language->get('error_permission'); } if (!$json) { $this->load->model('setting/modification'); $this->model_setting_modification->editStatus($modification_id, false); $json['success'] = $this->language->get('text_success'); } $this->response->addHeader('Content-Type: application/json'); $this->response->setOutput(json_encode($json)); } /** * Delete * * @return void */ public function delete(): void { $this->load->language('marketplace/modification'); $json = []; if (isset($this->request->post['selected'])) { $selected = (array)$this->request->post['selected']; } else { $selected = []; } if (!$this->user->hasPermission('modify', 'marketplace/modification')) { $json['error'] = $this->language->get('error_permission'); } if (!$json) { $this->load->model('setting/modification'); foreach ($selected as $modification_id) { $this->model_setting_modification->deleteModification($modification_id); } $json['success'] = $this->language->get('text_success'); } $this->response->addHeader('Content-Type: application/json'); $this->response->setOutput(json_encode($json)); } }