reverb.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. <?php
  2. class ControllerExtensionModuleReverb extends Controller {
  3. private $error = [];
  4. // -------------------------------------------------------------------------
  5. // Main settings page
  6. // -------------------------------------------------------------------------
  7. public function index() {
  8. $this->load->language('extension/module/reverb');
  9. $this->load->model('extension/module/reverb');
  10. $this->load->model('setting/setting');
  11. $this->load->model('catalog/category');
  12. $this->document->setTitle($this->language->get('heading_title'));
  13. if ($this->request->server['REQUEST_METHOD'] === 'POST' && $this->validate()) {
  14. $this->model_setting_setting->editSetting('module_reverb', $this->request->post);
  15. // Save category mappings separately (they come as a sub-array)
  16. if (isset($this->request->post['module_reverb_category_mappings'])) {
  17. $this->model_extension_module_reverb->saveCategoryMappings(
  18. $this->request->post['module_reverb_category_mappings']
  19. );
  20. }
  21. $this->session->data['success'] = $this->language->get('text_success');
  22. $this->response->redirect($this->url->link('extension/module/reverb', 'user_token=' . $this->session->data['user_token'], true));
  23. }
  24. $data = $this->buildBreadcrumbs();
  25. // Language strings required by the view
  26. $lang_keys = [
  27. 'tab_settings', 'tab_categories', 'tab_log',
  28. 'text_api_settings', 'text_shipping_settings', 'text_sync_settings', 'text_manual_sync',
  29. 'entry_api_token', 'help_api_token',
  30. 'entry_status', 'entry_sync_direction',
  31. 'text_sync_push', 'text_sync_both',
  32. 'entry_shipping_domestic', 'help_shipping_domestic',
  33. 'entry_shipping_international', 'help_shipping_international',
  34. 'help_sync_categories', 'button_sync_now',
  35. 'text_category_mapping_help', 'text_no_categories',
  36. 'column_oc_category', 'column_reverb_category',
  37. 'column_date', 'column_product', 'column_direction', 'column_status', 'column_message',
  38. 'text_push', 'text_pull', 'text_error', 'text_no_log',
  39. 'text_success', 'text_log_success',
  40. 'error_warning', 'error_api_token',
  41. ];
  42. foreach ($lang_keys as $key) {
  43. $data[$key] = $this->language->get($key);
  44. }
  45. // Global OC strings
  46. $data['text_edit'] = $this->language->get('text_edit');
  47. $data['text_enabled'] = $this->language->get('text_enabled');
  48. $data['text_disabled'] = $this->language->get('text_disabled');
  49. $data['button_save'] = $this->language->get('button_save');
  50. $data['button_cancel'] = $this->language->get('button_cancel');
  51. // Pull saved settings into $data
  52. $fields = [
  53. 'module_reverb_api_token',
  54. 'module_reverb_status',
  55. 'module_reverb_sync_direction',
  56. 'module_reverb_sync_categories',
  57. 'module_reverb_shipping_domestic',
  58. 'module_reverb_shipping_international',
  59. ];
  60. foreach ($fields as $key) {
  61. $data[$key] = $this->request->post[$key] ?? $this->config->get($key);
  62. }
  63. // Defaults
  64. $data['module_reverb_sync_direction'] = $data['module_reverb_sync_direction'] ?? 'push';
  65. $data['module_reverb_sync_categories'] = $data['module_reverb_sync_categories'] ?? [];
  66. $data['module_reverb_shipping_domestic'] = $data['module_reverb_shipping_domestic'] ?? '0.00';
  67. $data['module_reverb_shipping_international'] = $data['module_reverb_shipping_international'] ?? '0.00';
  68. // All OC categories for the multi-select
  69. $data['categories'] = $this->getCategoryTree();
  70. // Category mappings for the mapping tab
  71. $data['category_mappings'] = $this->model_extension_module_reverb->getCategoryMappings();
  72. $data['reverb_categories'] = $this->model_extension_module_reverb->getReverbCategories();
  73. $data['module_reverb_category_mappings'] = $data['category_mappings'];
  74. // Sync log
  75. $data['sync_log'] = $this->model_extension_module_reverb->getSyncLog(200);
  76. // Alerts
  77. $data['error_warning'] = $this->error['warning'] ?? '';
  78. $data['success'] = $this->session->data['success'] ?? '';
  79. unset($this->session->data['success']);
  80. $data['action'] = $this->url->link('extension/module/reverb', 'user_token=' . $this->session->data['user_token'], true);
  81. $data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true);
  82. $data['sync_url'] = $this->url->link('extension/module/reverb/sync', 'user_token=' . $this->session->data['user_token'], true);
  83. $data['categories_url'] = $this->url->link('extension/module/reverb/reverbCategories', 'user_token=' . $this->session->data['user_token'], true);
  84. $data['header'] = $this->load->controller('common/header');
  85. $data['column_left'] = $this->load->controller('common/column_left');
  86. $data['footer'] = $this->load->controller('common/footer');
  87. $this->response->setOutput($this->load->view('extension/module/reverb', $data));
  88. }
  89. // -------------------------------------------------------------------------
  90. // Install / Uninstall
  91. // -------------------------------------------------------------------------
  92. public function install() {
  93. $this->load->model('extension/module/reverb');
  94. $this->model_extension_module_reverb->install();
  95. // Register events for order pulling (admin side)
  96. $this->load->model('setting/event');
  97. $this->model_setting_event->addEvent(
  98. 'reverb',
  99. 'admin/model/catalog/product/editProduct/after',
  100. 'extension/module/reverb/eventProductSave'
  101. );
  102. $this->model_setting_event->addEvent(
  103. 'reverb',
  104. 'admin/model/catalog/product/addProduct/after',
  105. 'extension/module/reverb/eventProductAddSave'
  106. );
  107. }
  108. public function uninstall() {
  109. $this->load->model('extension/module/reverb');
  110. $this->model_extension_module_reverb->uninstall();
  111. $this->load->model('setting/event');
  112. $this->model_setting_event->deleteEventByCode('reverb');
  113. }
  114. // -------------------------------------------------------------------------
  115. // Event handlers (called by OC event system on product save)
  116. // -------------------------------------------------------------------------
  117. public function eventProductSave(&$route, &$args, &$output) {
  118. $product_id = (int)$args[0];
  119. $this->saveProductReverb($product_id);
  120. }
  121. public function eventProductAddSave(&$route, &$args, &$output) {
  122. // $output holds the new product_id for addProduct
  123. $product_id = (int)$output;
  124. if ($product_id) {
  125. $this->saveProductReverb($product_id);
  126. }
  127. }
  128. private function saveProductReverb($product_id) {
  129. if (!isset($this->request->post['reverb_sync_enabled'])) {
  130. return;
  131. }
  132. $this->load->model('extension/module/reverb');
  133. $this->model_extension_module_reverb->saveProductMap($product_id, [
  134. 'sync_enabled' => (int)(bool)$this->request->post['reverb_sync_enabled'],
  135. 'condition_uuid' => $this->request->post['reverb_condition_uuid'] ?? '',
  136. 'reverb_category_uuid' => $this->request->post['reverb_category_uuid'] ?? '',
  137. ]);
  138. }
  139. // -------------------------------------------------------------------------
  140. // Manual sync (AJAX)
  141. // -------------------------------------------------------------------------
  142. public function sync() {
  143. $this->load->language('extension/module/reverb');
  144. $this->load->model('extension/module/reverb');
  145. $json = ['success' => false];
  146. if (!$this->user->hasPermission('modify', 'extension/module/reverb')) {
  147. $json['error'] = $this->language->get('error_permission');
  148. $this->response->addHeader('Content-Type: application/json');
  149. $this->response->setOutput(json_encode($json));
  150. return;
  151. }
  152. $settings = $this->buildSettings();
  153. if (empty($settings['api_token'])) {
  154. $json['error'] = $this->language->get('error_api_token');
  155. $this->response->addHeader('Content-Type: application/json');
  156. $this->response->setOutput(json_encode($json));
  157. return;
  158. }
  159. try {
  160. $allowed_categories = $this->config->get('module_reverb_sync_categories') ?? [];
  161. $products = $this->model_extension_module_reverb->getSyncEnabledProducts((array)$allowed_categories);
  162. } catch (Exception $e) {
  163. $json['error'] = 'Database error: ' . $e->getMessage();
  164. $this->response->addHeader('Content-Type: application/json');
  165. $this->response->setOutput(json_encode($json));
  166. return;
  167. }
  168. $pushed = 0;
  169. $errors = 0;
  170. foreach ($products as $product) {
  171. try {
  172. $this->model_extension_module_reverb->syncProductToReverb($product, $product, $settings);
  173. $this->model_extension_module_reverb->log($product['product_id'], 'push', 'success', 'Synced: ' . $product['name']);
  174. $pushed++;
  175. } catch (Exception $e) {
  176. $this->model_extension_module_reverb->log($product['product_id'], 'push', 'error', $e->getMessage());
  177. $errors++;
  178. }
  179. }
  180. $json['success'] = true;
  181. $json['message'] = sprintf($this->language->get('text_sync_complete'), $pushed, $errors);
  182. $this->response->addHeader('Content-Type: application/json');
  183. $this->response->setOutput(json_encode($json));
  184. }
  185. // -------------------------------------------------------------------------
  186. // Per-product Reverb tab (AJAX — loaded into product edit page)
  187. // URL: extension/module/reverb/productTab?product_id=N
  188. // -------------------------------------------------------------------------
  189. public function productTab() {
  190. if (!$this->user->isLogged()) {
  191. $this->response->setOutput('');
  192. return;
  193. }
  194. $this->load->language('extension/module/reverb');
  195. $this->load->model('extension/module/reverb');
  196. $product_id = isset($this->request->get['product_id']) ? (int)$this->request->get['product_id'] : 0;
  197. $reverb_row = $this->model_extension_module_reverb->getProductMap($product_id);
  198. $data = [
  199. 'reverb_sync_enabled' => $reverb_row ? (int)$reverb_row['sync_enabled'] : 0,
  200. 'reverb_condition_uuid' => $reverb_row ? $reverb_row['condition_uuid'] : '',
  201. 'reverb_category_uuid' => $reverb_row ? $reverb_row['reverb_category_uuid'] : '',
  202. 'reverb_listing_id' => $reverb_row ? $reverb_row['reverb_listing_id'] : '',
  203. 'reverb_conditions' => $this->model_extension_module_reverb->getListingConditions(),
  204. 'reverb_categories' => $this->model_extension_module_reverb->getReverbCategories(),
  205. ];
  206. $this->response->setOutput($this->load->view('extension/module/reverb_product', $data));
  207. }
  208. // -------------------------------------------------------------------------
  209. // Reverb categories (AJAX — for category mapping dropdowns)
  210. // -------------------------------------------------------------------------
  211. public function reverbCategories() {
  212. $this->load->model('extension/module/reverb');
  213. $categories = $this->model_extension_module_reverb->getReverbCategories();
  214. $this->response->addHeader('Content-Type: application/json');
  215. $this->response->setOutput(json_encode(['categories' => $categories]));
  216. }
  217. // -------------------------------------------------------------------------
  218. // Per-product tab (loaded inline via OCMOD template include)
  219. // The data is passed through the OCMOD PHP patch, not via a separate request.
  220. // -------------------------------------------------------------------------
  221. // -------------------------------------------------------------------------
  222. // Helpers
  223. // -------------------------------------------------------------------------
  224. private function validate() {
  225. if (!$this->user->hasPermission('modify', 'extension/module/reverb')) {
  226. $this->error['warning'] = $this->language->get('error_permission');
  227. }
  228. $token = $this->request->post['module_reverb_api_token'] ?? '';
  229. if (empty(trim($token))) {
  230. $this->error['warning'] = $this->language->get('error_api_token');
  231. }
  232. return empty($this->error);
  233. }
  234. private function buildBreadcrumbs() {
  235. return [
  236. 'heading_title' => $this->language->get('heading_title'),
  237. 'breadcrumbs' => [
  238. [
  239. 'text' => $this->language->get('text_home'),
  240. 'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true),
  241. ],
  242. [
  243. 'text' => $this->language->get('text_extension'),
  244. 'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true),
  245. ],
  246. [
  247. 'text' => $this->language->get('heading_title'),
  248. 'href' => $this->url->link('extension/module/reverb', 'user_token=' . $this->session->data['user_token'], true),
  249. ],
  250. ],
  251. ];
  252. }
  253. private function buildSettings() {
  254. return [
  255. 'api_token' => $this->config->get('module_reverb_api_token'),
  256. 'sync_direction' => $this->config->get('module_reverb_sync_direction') ?? 'push',
  257. 'shipping_domestic' => $this->config->get('module_reverb_shipping_domestic') ?? '0',
  258. 'shipping_international' => $this->config->get('module_reverb_shipping_international') ?? '0',
  259. 'currency' => $this->config->get('config_currency') ?? 'AUD',
  260. 'store_url' => $this->config->get('config_url') ?? '',
  261. ];
  262. }
  263. private function getCategoryTree() {
  264. $language_id = (int)$this->config->get('config_language_id');
  265. $query = $this->db->query("
  266. SELECT c.category_id, c.parent_id, cd.name
  267. FROM `" . DB_PREFIX . "category` c
  268. LEFT JOIN `" . DB_PREFIX . "category_description` cd
  269. ON cd.category_id = c.category_id AND cd.language_id = '" . $language_id . "'
  270. WHERE c.status = 1
  271. ORDER BY c.parent_id ASC, cd.name ASC
  272. ");
  273. $all = $query->rows;
  274. $byPid = [];
  275. foreach ($all as $row) {
  276. $byPid[(int)$row['parent_id']][] = $row;
  277. }
  278. $result = [];
  279. $stack = [[0, '']];
  280. while ($stack) {
  281. [$pid, $indent] = array_pop($stack);
  282. if (empty($byPid[$pid])) {
  283. continue;
  284. }
  285. foreach (array_reverse($byPid[$pid]) as $cat) {
  286. $result[] = [
  287. 'category_id' => $cat['category_id'],
  288. 'name' => $indent . $cat['name'],
  289. ];
  290. array_push($stack, [(int)$cat['category_id'], $indent . '&nbsp;&nbsp;&nbsp;']);
  291. }
  292. }
  293. return $result;
  294. }
  295. }