diff --git a/_build/build.config.php b/_build/build.config.php index 0cbcde4d3..c52759c13 100644 --- a/_build/build.config.php +++ b/_build/build.config.php @@ -54,5 +54,6 @@ 'settings', 'chunks', 'statuses', + 'utilites', 'setup', ]; diff --git a/_build/data/transport.menu.php b/_build/data/transport.menu.php index ae476147b..5ac254a89 100644 --- a/_build/data/transport.menu.php +++ b/_build/data/transport.menu.php @@ -36,11 +36,11 @@ 'menuindex' => 3, 'action' => 'mgr/help', ], - 'ms2_utilites' => [ - 'description' => 'ms2_utilites_desc', + 'ms2_utilities' => [ + 'description' => 'ms2_utilities_desc', 'parent' => 'minishop2', 'menuindex' => 4, - 'action' => 'mgr/utilites', + 'action' => 'mgr/utilities', ], ]; diff --git a/_build/data/transport.settings.php b/_build/data/transport.settings.php index 89a8749e0..fea4e56bd 100644 --- a/_build/data/transport.settings.php +++ b/_build/data/transport.settings.php @@ -357,6 +357,17 @@ 'value' => '2,3', 'xtype' => 'textfield', 'area' => 'ms2_statuses', + ], + + 'ms2_utility_import_fields' => [ + 'value' => 'pagetitle,parent,price,article', + 'xtype' => 'textfield', + 'area' => 'ms2_import', + ], + 'ms2_utility_import_fields_delimiter' => [ + 'value' => ';', + 'xtype' => 'textfield', + 'area' => 'ms2_import', ] ]; diff --git a/_build/resolvers/resolve.utilites.php b/_build/resolvers/resolve.utilites.php new file mode 100644 index 000000000..2ed9fb63d --- /dev/null +++ b/_build/resolvers/resolve.utilites.php @@ -0,0 +1,23 @@ +xpdo) { + $modx = $transport->xpdo; + // fix for removing installed old incorrect menu + switch ($options[xPDOTransport::PACKAGE_ACTION]) { + case xPDOTransport::ACTION_INSTALL: + case xPDOTransport::ACTION_UPGRADE: + $menu = $modx->getObject('modMenu', ['text' => 'ms2_utilites']); + if ($menu) { + $menu->remove(); + unlink(MODX_ASSETS_PATH . 'components/minishop2/js/mgr/utilites/gallery/panel.js'); + rmdir(MODX_ASSETS_PATH . 'components/minishop2/js/mgr/utilites/gallery/'); + unlink(MODX_ASSETS_PATH . 'components/minishop2/js/mgr/utilites/panel.js'); + rmdir(MODX_ASSETS_PATH . 'components/minishop2/js/mgr/utilites/'); + } + break; + } +} +return true; diff --git a/assets/components/minishop2/css/mgr/utilites/gallery.css b/assets/components/minishop2/css/mgr/utilities/gallery.css similarity index 100% rename from assets/components/minishop2/css/mgr/utilites/gallery.css rename to assets/components/minishop2/css/mgr/utilities/gallery.css diff --git a/assets/components/minishop2/js/mgr/utilites/gallery/panel.js b/assets/components/minishop2/js/mgr/utilites/gallery/panel.js deleted file mode 100644 index 1ef65dd75..000000000 --- a/assets/components/minishop2/js/mgr/utilites/gallery/panel.js +++ /dev/null @@ -1,135 +0,0 @@ -miniShop2.panel.UtilitesGallery = function (config) { - config = config || {}; - - Ext.apply(config, { - cls: 'container form-with-labels', - autoHeight: true, - url: miniShop2.config.connector_url, - saveMsg: _('ms2_utilites_gallery_updating'), - - progress:true, - baseParams: { - action: 'mgr/utilites/gallery/update' - }, - items: [{ - layout: 'form', - cls: 'main-wrapper', - labelWidth: 200, - labelAlign: 'left', - border: false, - buttonAlign: 'left', - style: 'padding: 0 0 0 7px', - items: [ - { - html: String.format( - _('ms2_utilites_gallery_information'), - miniShop2.config.utility_gallery_source_name, - miniShop2.config.utility_gallery_source_id, - miniShop2.config.utility_gallery_total_products, - miniShop2.config.utility_gallery_total_products_files - ), - }, - { - xtype: 'fieldset', - title: _('ms2_utilites_params'), - id: 'ms2-utilites-gallery-params', - cls: 'x-fieldset-checkbox-toggle', - style: 'margin: 5px 0 15px ', - collapsible: true, - collapsed: true, - stateful: true, - labelAlign: 'top', - stateEvents: ['collapse', 'expand'], - items: [ - { - html: miniShop2.config.utility_gallery_thumbnails - }, - ] - }, - { - name: 'limit', - xtype: 'numberfield', - value: 10, - width: 80, - fieldLabel: _('ms2_utilites_gallery_for_step') - }, - { - name: 'offset', - xtype: 'numberfield', - value: 0, - hidden: true, - }, - { - xtype: 'button', - style: 'margin: 15px 0 0 2px', - text: '  ' + _('ms2_utilites_gallery_refresh'), - handler: function() { - var form = this.getForm(); - form.setValues({ - offset: 0 - }); - this.submit(this); - }, scope: this - }, - { - style: 'padding: 15px 0', - html: '\ - \ - ' - } - ] - }], - listeners: { - success: {fn: function(response) { - var data = response.result.object; - var form = this.getForm(); - this.updateProgress(data); - - if (!data.done) { - form.setValues({ - offset: Number(data.offset) - }); - this.submit(this); - } - else { - MODx.msg.status({ - title: _('ms2_utilites_gallery_done'), - message: _('ms2_utilites_gallery_done_message'), - delay: 5 - }); - } - },scope: this} - } - }); - miniShop2.panel.UtilitesGallery.superclass.constructor.call(this, config); -}; - -Ext.extend(miniShop2.panel.UtilitesGallery, MODx.FormPanel,{ - - updateProgress: function (data) { - const progressblock = document.getElementById('ms-utility-gallery-range_outer'); - const progresslabel = document.getElementById('ms-utility-gallery-label'); - const progressbar = document.getElementById('ms-utility-gallery-progress-bar'); - const progressiteration = document.getElementById('ms-utility-gallery-iteration'); - progressblock.style.visibility = 'visible'; - - if(data.done) { - progresslabel.innerHTML = '100%'; - progressbar.style.width = '100%'; - progressiteration.style.visibility = 'hidden'; - } else { - let progress = (parseFloat((data.offset/data.total) * 100)).toFixed(2); - progresslabel.innerHTML = progress + '%'; - progressbar.style.width = progress + '%'; - - // count iterations - const totalIterations = Math.ceil(data.total/data.limit); - const currentIteration = data.offset/data.limit; - progressiteration.innerHTML = currentIteration + "/" + totalIterations; - } - } -}); -Ext.reg('minishop2-utilites-gallery', miniShop2.panel.UtilitesGallery); diff --git a/assets/components/minishop2/js/mgr/utilities/gallery/panel.js b/assets/components/minishop2/js/mgr/utilities/gallery/panel.js new file mode 100644 index 000000000..32920bb40 --- /dev/null +++ b/assets/components/minishop2/js/mgr/utilities/gallery/panel.js @@ -0,0 +1,137 @@ +miniShop2.panel.UtilitiesGallery = function (config) { + config = config || {}; + + Ext.apply(config, { + cls: 'container form-with-labels', + autoHeight: true, + url: miniShop2.config.connector_url, + saveMsg: _('ms2_utilities_gallery_updating'), + + progress: true, + baseParams: { + action: 'mgr/utilities/gallery/update' + }, + items: [{ + layout: 'form', + cls: 'main-wrapper', + labelWidth: 200, + labelAlign: 'left', + border: false, + buttonAlign: 'left', + style: 'padding: 0 0 0 7px', + items: [ + { + html: String.format( + _('ms2_utilities_gallery_information'), + miniShop2.config.utility_gallery_source_name, + miniShop2.config.utility_gallery_source_id, + miniShop2.config.utility_gallery_total_products, + miniShop2.config.utility_gallery_total_products_files + ), + }, + { + xtype: 'fieldset', + title: _('ms2_utilities_params'), + id: 'ms2-utilities-gallery-params', + cls: 'x-fieldset-checkbox-toggle', + style: 'margin: 5px 0 15px ', + collapsible: true, + collapsed: true, + stateful: true, + labelAlign: 'top', + stateEvents: ['collapse', 'expand'], + items: [ + { + html: miniShop2.config.utility_gallery_thumbnails + }, + ] + }, + { + name: 'limit', + xtype: 'numberfield', + value: 10, + width: 80, + fieldLabel: _('ms2_utilities_gallery_for_step') + }, + { + name: 'offset', + xtype: 'numberfield', + value: 0, + hidden: true, + }, + { + xtype: 'button', + style: 'margin: 15px 0 0 2px', + text: '  ' + _('ms2_utilities_gallery_refresh'), + handler: function () { + var form = this.getForm(); + form.setValues({ + offset: 0 + }); + this.submit(this); + }, scope: this + }, + { + style: 'padding: 15px 0', + html: '\ + \ + ' + } + ] + }], + listeners: { + success: { + fn: function (response) { + var data = response.result.object; + var form = this.getForm(); + this.updateProgress(data); + + if (!data.done) { + form.setValues({ + offset: Number(data.offset) + }); + this.submit(this); + } + else { + MODx.msg.status({ + title: _('ms2_utilities_gallery_done'), + message: _('ms2_utilities_gallery_done_message'), + delay: 5 + }); + } + }, scope: this + } + } + }); + miniShop2.panel.UtilitiesGallery.superclass.constructor.call(this, config); +}; + +Ext.extend(miniShop2.panel.UtilitiesGallery, MODx.FormPanel, { + + updateProgress: function (data) { + const progressblock = document.getElementById('ms-utility-gallery-range_outer'); + const progresslabel = document.getElementById('ms-utility-gallery-label'); + const progressbar = document.getElementById('ms-utility-gallery-progress-bar'); + const progressiteration = document.getElementById('ms-utility-gallery-iteration'); + progressblock.style.visibility = 'visible'; + + if (data.done) { + progresslabel.innerHTML = '100%'; + progressbar.style.width = '100%'; + progressiteration.style.visibility = 'hidden'; + } else { + let progress = (parseFloat((data.offset / data.total) * 100)).toFixed(2); + progresslabel.innerHTML = progress + '%'; + progressbar.style.width = progress + '%'; + + // count iterations + const totalIterations = Math.ceil(data.total / data.limit); + const currentIteration = data.offset / data.limit; + progressiteration.innerHTML = currentIteration + "/" + totalIterations; + } + } +}); +Ext.reg('minishop2-utilities-gallery', miniShop2.panel.UtilitiesGallery); diff --git a/assets/components/minishop2/js/mgr/utilities/import/panel.js b/assets/components/minishop2/js/mgr/utilities/import/panel.js new file mode 100644 index 000000000..0ff6cc435 --- /dev/null +++ b/assets/components/minishop2/js/mgr/utilities/import/panel.js @@ -0,0 +1,223 @@ +miniShop2.panel.UtilitiesImport = function (config) { + config = config || {} + + Ext.apply(config, { + cls: 'container form-with-labels', + autoHeight: true, + url: miniShop2.config.connector_url, + progress: true, + id: 'ms2-panel-import', + baseParams: { + action: 'mgr/utilities/import/import' + }, + items: [{ + layout: 'column', + border: false, + anchor: '100%', + cls: 'main-wrapper', + labelAlign: 'top', + buttonAlign: 'left', + style: 'padding: 0 0 0 7px', + items: [{ + columnWidth: 0.5, + layout: 'form', + defaults: { msgTarget: 'under' }, + border: false, + style: { margin: '0' }, + items: [ + { + xtype: 'modx-combo-browser', + fieldLabel: _('ms2_utilities_import_label_file'), + emptyText: _('ms2_utilities_import_label_file_empty'), + anchor: '81%', + name: 'importfile', + allowBlank: false, + }, + { + layout: 'column', + items: [{ + columnWidth: 0.8, + layout: 'form', + border: false, + style: { margin: '0' }, + items: [ + { + xtype: 'textfield', + name: 'fields', + value: miniShop2.config.utility_import_fields, + width: '99%', + fieldLabel: _('ms2_utilities_import_label_fields'), + allowBlank: false, + }, + { + xtype: 'textfield', + name: 'delimiter', + value: miniShop2.config.utility_import_fields_delimiter, + width: '99%', + allowBlank: false, + fieldLabel: _('ms2_utilities_import_label_delimiter'), + } + ] + }, + { + columnWidth: 0.2, + layout: 'form', + border: false, + style: { margin: '20px 0 0 15px' }, + items: [ + { + xtype: 'button', + style: 'padding: 4px 10px 7px; margin: 18px 0 0 0', + tooltip: _('ms2_utilities_import_save_fields'), + text: '', + handler: function () { + this.saveConfig(this) + }, scope: this + } + ] + + }] + }, + { + xtype: 'xcheckbox', + name: 'update', + value: 1, + id: 'ms-utilities-import-update', + boxLabel: _('ms2_utilities_import_update_products'), + labelAlign: 'right', + listeners: { + check: { + fn: this.onUpdateNeed, + scope: this + }, + } + }, + { + xtype: 'textfield', + name: 'key', + value: 'article', + width: '99%', + hidden: true, + id: 'ms-utilities-import-key', + fieldLabel: _('ms2_utilities_import_update_key'), + }, + { + xtype: 'xcheckbox', + name: 'debug', + value: 1, + hideLabel: true, + id: 'ms-utilities-import-debug', + boxLabel: _('ms2_utilities_import_debug'), + labelAlign: 'right', + }, + { + xtype: 'xcheckbox', + name: 'scheduler', + value: 1, + hideLabel: true, + id: 'ms-utilities-import-scheduler', + boxLabel: _('ms2_utilities_import_use_scheduler'), + labelAlign: 'right', + }, + { + xtype: 'xcheckbox', + name: 'skip_header', + value: 0, + hideLabel: true, + id: 'ms-utilities-import-skip_header', + boxLabel: _('ms2_utilities_import_skip_header'), + labelAlign: 'right', + }, + { + xtype: 'button', + style: 'margin: 25px 0 0 2px', + text: '  ' + _('ms2_utilities_import_submit'), + handler: function () { + this.submit(this) + }, scope: this + } + ] + }/*, { + columnWidth: 0.5, + layout: 'form', + defaults: { msgTarget: 'under' }, + border: false, + style: { margin: '0 0 0 20px' }, + items: [ + { + xtype: 'fieldset', + title: 'Инструкция', + id: 'ms2-utilities-import-instruction', + cls: 'x-fieldset-checkbox-toggle', + style: 'margin: 5px 0 15px; padding: 20px; ', + collapsible: true, + collapsed: true, + stateful: true, + labelAlign: 'top', + stateEvents: ['collapse', 'expand'], + items: [ + { + html: '' + }, + ] + } + ] + }*/] + }], + listeners: { + success: { + fn: function (response) { + const data = response.result + const alert = data.success === true ? _('success') : _('error') + MODx.msg.alert(alert, data.message) + }, scope: this + }, + failure: { + fn: function (response) { + }, scope: this + } + } + }) + + miniShop2.panel.UtilitiesImport.superclass.constructor.call(this, config) +} + +Ext.extend(miniShop2.panel.UtilitiesImport, MODx.FormPanel, { + + onUpdateNeed: function (cb) { + var updateKey = Ext.getCmp('ms-utilities-import-key') + if (cb.getValue()) { + updateKey.show() + } else { + updateKey.hide() + } + }, + + saveConfig: function () { + var form = this.getForm() + var values = form.getValues() + + MODx.Ajax.request({ + url: miniShop2.config['connector_url'], + params: { + action: 'mgr/utilities/import/saveconfig', + fields: values.fields, + delimiter: values.delimiter + }, + listeners: { + success: { + fn: function (r) { + MODx.msg.status({ + title: _('ms2_utilities_import_save_fields_title'), + message: _('ms2_utilities_import_save_fields_message'), + delay: 7 + }) + }, scope: this + } + } + }) + + } + +}) +Ext.reg('minishop2-utilities-import', miniShop2.panel.UtilitiesImport) diff --git a/assets/components/minishop2/js/mgr/utilites/panel.js b/assets/components/minishop2/js/mgr/utilities/panel.js similarity index 54% rename from assets/components/minishop2/js/mgr/utilites/panel.js rename to assets/components/minishop2/js/mgr/utilities/panel.js index 8c13cd0c0..dc4499a79 100644 --- a/assets/components/minishop2/js/mgr/utilites/panel.js +++ b/assets/components/minishop2/js/mgr/utilities/panel.js @@ -1,15 +1,15 @@ -miniShop2.panel.Utilites = function (config) { +miniShop2.panel.Utilities = function (config) { config = config || {}; - Ext.apply(config, { - cls: 'container', + Ext.apply(config, { + cls: 'container', items: [{ - html: '

' + _('minishop2') + ' :: ' + _('ms2_utilites') + '

', + html: '

' + _('minishop2') + ' :: ' + _('ms2_utilities') + '

', cls: 'modx-page-header', }, { xtype: 'modx-tabs', - id: 'minishop2-utilites-tabs', + id: 'minishop2-utilities-tabs', stateful: true, - stateId: 'minishop2-utilites-tabs', + stateId: 'minishop2-utilities-tabs', stateEvents: ['tabchange'], cls: 'minishop2-panel', getState: function () { @@ -18,34 +18,32 @@ miniShop2.panel.Utilites = function (config) { }; }, items: [{ - title: _('ms2_utilites_gallery'), + title: _('ms2_utilities_gallery'), layout: 'anchor', items: [{ - html: _('ms2_utilites_gallery_intro'), + html: _('ms2_utilities_gallery_intro'), bodyCssClass: 'panel-desc', }, { - xtype: 'minishop2-utilites-gallery', + xtype: 'minishop2-utilities-gallery', cls: 'main-wrapper', }] }, - /* - // todo { - title: _('ms2_utilites_import'), + title: _('ms2_utilities_import'), layout: 'anchor', items: [{ - html: _('ms2_utilites_import_intro'), + html: _('ms2_utilities_import_intro'), bodyCssClass: 'panel-desc', }, { - xtype: 'minishop2-utilites-import', + xtype: 'minishop2-utilities-import', cls: 'main-wrapper', }] - }*/ - ] + } + ] }] }); - miniShop2.panel.Utilites.superclass.constructor.call(this, config); + miniShop2.panel.Utilities.superclass.constructor.call(this, config); }; -Ext.extend(miniShop2.panel.Utilites, MODx.Panel); -Ext.reg('minishop2-utilites', miniShop2.panel.Utilites); +Ext.extend(miniShop2.panel.Utilities, MODx.Panel); +Ext.reg('minishop2-utilities', miniShop2.panel.Utilities); diff --git a/core/components/minishop2/controllers/mgr/utilites.class.php b/core/components/minishop2/controllers/mgr/utilities.class.php similarity index 72% rename from core/components/minishop2/controllers/mgr/utilites.class.php rename to core/components/minishop2/controllers/mgr/utilities.class.php index e6c6c1ef9..0ae3594af 100644 --- a/core/components/minishop2/controllers/mgr/utilites.class.php +++ b/core/components/minishop2/controllers/mgr/utilities.class.php @@ -4,14 +4,14 @@ require_once dirname(__FILE__, 2) . '/manager.class.php'; } -class Minishop2MgrUtilitesManagerController extends msManagerController +class Minishop2MgrutilitiesManagerController extends msManagerController { /** * @return string */ public function getPageTitle() { - return $this->modx->lexicon('ms2_utilites') . ' | miniShop2'; + return $this->modx->lexicon('ms2_utilities') . ' | miniShop2'; } /** @@ -27,12 +27,11 @@ public function getLanguageTopics() */ public function loadCustomCssJs() { - - $this->addCss($this->miniShop2->config['cssUrl'] . 'mgr/utilites/gallery.css'); - + $this->addCss($this->miniShop2->config['cssUrl'] . 'mgr/utilities/gallery.css'); $this->addJavascript($this->miniShop2->config['jsUrl'] . 'mgr/minishop2.js'); - $this->addJavascript($this->miniShop2->config['jsUrl'] . 'mgr/utilites/panel.js'); - $this->addJavascript($this->miniShop2->config['jsUrl'] . 'mgr/utilites/gallery/panel.js'); + $this->addJavascript($this->miniShop2->config['jsUrl'] . 'mgr/utilities/panel.js'); + $this->addJavascript($this->miniShop2->config['jsUrl'] . 'mgr/utilities/gallery/panel.js'); + $this->addJavascript($this->miniShop2->config['jsUrl'] . 'mgr/utilities/import/panel.js'); $config = $this->miniShop2->config; @@ -42,7 +41,7 @@ public function loadCustomCssJs() $config['utility_gallery_source_id'] = $productSource; $config['utility_gallery_source_name'] = $source->get('name'); - $properties = $source->get('properties'); + $properties = $source->get('properties'); $propertiesString = ''; foreach (json_decode($properties['thumbnails']['value'], true) as $key => $value) { $propertiesString .= "$key: " . json_encode($value) . "
"; @@ -54,12 +53,15 @@ public function loadCustomCssJs() $config['utility_gallery_total_products'] = $this->modx->getCount('msProduct', ['class_key' => 'msProduct']); $config['utility_gallery_total_products_files'] = $this->modx->getCount('msProductFile', ['parent' => 0]); + // get params for import + $config['utility_import_fields'] = $this->getOption('ms2_utility_import_fields', null, 'pagetitle,parent,price,article', true); + $config['utility_import_fields_delimiter'] = $this->getOption('ms2_utility_import_fields_delimiter', null, ';', true); + $this->addHtml( '' ); diff --git a/core/components/minishop2/import/importCSV.php b/core/components/minishop2/import/importCSV.php new file mode 100644 index 000000000..8f500dbd6 --- /dev/null +++ b/core/components/minishop2/import/importCSV.php @@ -0,0 +1,276 @@ +modx = $modx; + $this->miniShop2 = $this->modx->getService('miniShop2'); + // Time limit + set_time_limit(600); + $tmp = 'Trying to set time limit = 600 sec: '; + $tmp .= ini_get('max_execution_time') == 600 ? 'done' : 'error'; + $this->modx->log(modX::LOG_LEVEL_INFO, $tmp); + } + + public function process($params) + { + $this->params['file'] = @$params['file']; + $this->params['fields'] = @$params['fields']; + $this->params['update'] = !empty($params['update']); + $this->params['key'] = @$params['key']; + $this->params['skip_header'] = @$params['skip_header']; + $this->params['is_debug'] = !empty($params['debug']); + $this->params['delimeter'] = $params['delimeter'] ?? ';'; + $this->params['keys'] = []; + $this->params['tv_enabled'] = false; + + + // Check required options + if (empty($this->params['fields'])) { + $error = $this->modx->lexicon('ms2_utilities_import_fields_ns'); + $this->modx->log(modX::LOG_LEVEL_ERROR, $error); + return $this->miniShop2->error($error); + } + if (empty($this->params['key'])) { + $error = $this->modx->lexicon('ms2_utilities_import_key_ns'); + $this->modx->log(modX::LOG_LEVEL_ERROR, $error); + return $this->miniShop2->error($error); + } + + $this->params['keys'] = array_map('trim', explode(',', strtolower($this->params['fields']))); + foreach ($this->params['keys'] as $v) { + if (preg_match('/^tv(\d+)$/', $v)) { + $this->params['tv_enabled'] = true; + break; + } + } + + // Check file + if (empty($this->params['file'])) { + $error = $this->modx->lexicon('ms2_utilities_import_file_ns'); + $this->modx->log(modX::LOG_LEVEL_ERROR, $error); + return $this->miniShop2->error($error); + } elseif (!preg_match('/\.csv$/i', $this->params['file'])) { + $error = $this->modx->lexicon('ms2_utilities_import_file_ext_err'); + $this->modx->log(modX::LOG_LEVEL_ERROR, $error); + return $this->miniShop2->error($error); + } + + $this->params['file'] = str_replace('//', '/', MODX_BASE_PATH . $this->params['file']); + if (!file_exists($this->params['file'])) { + $error = $this->modx->lexicon('ms2_utilities_import_file_nf', ['path' => $this->params['file']]); + $this->modx->log(modX::LOG_LEVEL_ERROR, $error); + return $this->miniShop2->error($error); + } + + $requiredFields = [ + 'parent', + 'pagetitle', + ]; + + foreach ($requiredFields as $rf) { + if (!in_array($rf, $this->params['keys'])) { + $error = $this->modx->lexicon('ms2_utilities_import_required_field', ['field' => $rf]); + return $this->miniShop2->error($error); + } + } + + + $this->import(); + + $message = $this->modx->lexicon('ms2_utilities_import_success', [ + 'total' => $this->rows, + 'created' => $this->created, + 'updated' => $this->updated + ]); + return $this->miniShop2->success($message); + } + + private function import() + { + $handle = fopen($this->params['file'], 'r'); + + while (($csv = fgetcsv($handle, 0, $this->params['delimeter'])) !== false) { + $this->rows++; + if (!empty($this->params['skip_header']) && $this->rows === 1) { + continue; + } + $this->processRow($csv); + + if ($this->params['is_debug'] && $this->rows === 1) { + $this->modx->log( + modX::LOG_LEVEL_INFO, + 'You in debug mode, so we process only 1 row. Time: ' . number_format( + microtime(true) - $this->modx->startTime, + 7 + ) . ' s' + ); + return true; + } + } + fclose($handle); + return true; + } + + private function processRow($csv) + { + $data = $gallery = []; + $this->modx->log(modX::LOG_LEVEL_INFO, "Raw data for import: \n" . print_r($csv, 1)); + + foreach ($this->params['keys'] as $k => $v) { + if (!isset($csv[$k])) { + $error = $this->modx->lexicon('ms2_utilities_file_field_nf', ['field' => $v, 'row' => $this->rows]); + $this->modx->log(modX::LOG_LEVEL_ERROR, $error); + return false; + } + if ($v == 'gallery') { + $gallery[] = $csv[$k]; + } elseif (isset($data[$v]) && !is_array($data[$v])) { + $data[$v] = [$data[$v], $csv[$k]]; + } elseif (isset($data[$v])) { + $data[$v][] = $csv[$k]; + } else { + $data[$v] = $csv[$k]; + } + } + $is_product = false; + + // Set default values + if (empty($data['class_key'])) { + $data['class_key'] = 'msProduct'; + } + if (empty($data['context_key'])) { + $parent = $this->modx->getObject('modResource', ['id' => $data['parent']]); + if (isset($data['parent']) && $parent) { + $data['context_key'] = $parent->get('context_key'); + } elseif (isset($this->modx->resource) && isset($this->modx->context)) { + $data['context_key'] = $this->modx->context->key; + } else { + $data['context_key'] = 'web'; + } + } + $data['tvs'] = $this->params['tv_enabled']; + $this->modx->log(modX::LOG_LEVEL_INFO, "Array with importing data: \n" . print_r($data, 1)); + + // Duplicate check + $q = $this->modx->newQuery($data['class_key']); + $q->where([ + 'deleted' => 0, + 'class_key' => $data['class_key'] + ]); + $q->select($data['class_key'] . '.id'); + if (strtolower($data['class_key']) === 'msproduct') { + $q->innerJoin('msProductData', 'Data', $data['class_key'] . '.id = Data.id'); + $is_product = true; + } + $tmp = $this->modx->getFields($data['class_key']); + $key = $this->params['key']; + if (isset($tmp[$key])) { + $q->where([$key => $data[$key]]); + } elseif ($is_product) { + $q->where(['Data.' . $key => $data[$key]]); + } + $q->prepare(); + $this->modx->log(modX::LOG_LEVEL_INFO, "SQL query for check for duplicate: \n" . $q->toSql()); + + $action = 'create'; + /** @var modResource $exists */ + $exists = $this->modx->getObject($data['class_key'], $q); + if ($exists) { + $this->modx->log(modX::LOG_LEVEL_INFO, "Key $key = $data[$key] has duplicate."); + if (!$this->params['update']) { + $this->modx->log(modX::LOG_LEVEL_ERROR, "Skipping line with $key = \"$data[$key]\" because update is disabled."); + if ($this->params['is_debug'] && $this->rows === 1) { + $this->modx->log( + modX::LOG_LEVEL_INFO, + 'You in debug mode, so we process only 1 row. Time: ' . number_format( + microtime(true) - $this->modx->startTime, + 7 + ) . ' s' + ); + return true; + } + } else { + $action = 'update'; + $data['id'] = $exists->id; + } + } + + $this->runAction($action, $data, $gallery); + } + + private function runAction($action, $data, $gallery = []) + { + $this->modx->error->reset(); + /** @var modProcessorResponse $response */ + $response = $this->modx->runProcessor('resource/' . $action, $data); + if ($response->isError()) { + $this->modx->log(modX::LOG_LEVEL_ERROR, "Error on $action: \n" . print_r($response->getAllErrors(), 1)); + } else { + if ($action == 'update') { + $this->updated++; + } else { + $this->created++; + } + + $resource = $response->getObject(); + $this->modx->log(modX::LOG_LEVEL_INFO, "Successful $action: \n" . print_r($resource, 1)); + + if (!empty($gallery)) { + // Process gallery images, if exists + $this->processGallery($resource, $gallery); + } + } + } + + private function processGallery($resource, $gallery) + { + if (!empty($gallery)) { + $this->modx->log(modX::LOG_LEVEL_INFO, "Importing images: \n" . print_r($gallery, 1)); + foreach ($gallery as $v) { + if (empty($v)) { + continue; + } + $image = str_replace('//', '/', MODX_BASE_PATH . $v); + if (!file_exists($image)) { + $this->modx->log( + modX::LOG_LEVEL_ERROR, + "Could not import image \"$v\" to gallery. File \"$image\" not found on server." + ); + } else { + $response = $this->modx->runProcessor( + 'gallery/upload', + ['id' => $resource['id'], 'name' => $v, 'file' => $image], + ['processors_path' => MODX_CORE_PATH . 'components/minishop2/processors/mgr/'] + ); + if ($response->isError()) { + $this->modx->log( + modX::LOG_LEVEL_ERROR, + "Error on upload \"$v\": \n" . print_r($response->getAllErrors(), 1) + ); + } else { + $this->modx->log( + modX::LOG_LEVEL_INFO, + "Successful upload \"$v\": \n" . print_r($response->getObject(), 1) + ); + } + } + } + } + } +} diff --git a/core/components/minishop2/lexicon/ru/default.inc.php b/core/components/minishop2/lexicon/ru/default.inc.php index 72994762c..5cdabe8f3 100644 --- a/core/components/minishop2/lexicon/ru/default.inc.php +++ b/core/components/minishop2/lexicon/ru/default.inc.php @@ -165,7 +165,7 @@ $_lang['ms2_system_settings'] = 'Системные настройки'; $_lang['ms2_system_settings_desc'] = 'Системные настройки miniShop2'; $_lang['ms2_type'] = 'Тип'; -$_lang['ms2_utilites'] = 'Утилиты'; -$_lang['ms2_utilites_desc'] = 'Инструменты разработчика'; +$_lang['ms2_utilities'] = 'Утилиты'; +$_lang['ms2_utilities_desc'] = 'Инструменты разработчика'; $_lang['ms2_vendors'] = 'Производители товаров'; $_lang['ms2_vendors_intro'] = 'Список возможных производителей товаров. То, что вы сюда добавите, можно выбрать в поле "vendor" товара.'; diff --git a/core/components/minishop2/lexicon/ru/manager.inc.php b/core/components/minishop2/lexicon/ru/manager.inc.php index 0b44e8d4b..396e102d0 100644 --- a/core/components/minishop2/lexicon/ru/manager.inc.php +++ b/core/components/minishop2/lexicon/ru/manager.inc.php @@ -152,16 +152,41 @@ $_lang['ms2_updatedon'] = 'Дата изменения'; $_lang['ms2_user'] = 'Пользователь'; $_lang['ms2_username'] = 'Логин'; -$_lang['ms2_utilites_gallery'] = 'Галерея'; -$_lang['ms2_utilites_gallery_done'] = 'Завершено'; -$_lang['ms2_utilites_gallery_done_message'] = 'Обновление превью изображений успешно завершено.'; -$_lang['ms2_utilites_gallery_err_noproducts'] = 'В каталоге нет товаров'; -$_lang['ms2_utilites_gallery_for_step'] = 'Обработать товаров за 1 шаг'; -$_lang['ms2_utilites_gallery_information'] = 'Выбранный источник файлов: {0}
Всего товаров: {2} шт.
Изображений: {3} шт. '; -$_lang['ms2_utilites_gallery_intro'] = 'Обновление всех изображений товаров согласно указанным параметрам.
Данная операция является трудозатратной, поэтому не указывайте большое число для одной итерации.'; -$_lang['ms2_utilites_gallery_refresh'] = 'Обновить'; -$_lang['ms2_utilites_gallery_updating'] = 'Обновлению превью'; -$_lang['ms2_utilites_params'] = 'Параметры '; +$_lang['ms2_utilities_gallery'] = 'Галерея'; +$_lang['ms2_utilities_gallery_done'] = 'Завершено'; +$_lang['ms2_utilities_gallery_done_message'] = 'Обновление превью изображений успешно завершено.'; +$_lang['ms2_utilities_gallery_err_noproducts'] = 'В каталоге нет товаров'; +$_lang['ms2_utilities_gallery_for_step'] = 'Обработать товаров за 1 шаг'; +$_lang['ms2_utilities_gallery_information'] = 'Выбранный источник файлов: {0}
Всего товаров: {2} шт.
Изображений: {3} шт. '; +$_lang['ms2_utilities_gallery_intro'] = 'Обновление всех изображений товаров согласно указанным параметрам.
Данная операция является трудозатратной, поэтому не указывайте большое число для одной итерации.'; +$_lang['ms2_utilities_gallery_refresh'] = 'Обновить'; +$_lang['ms2_utilities_gallery_updating'] = 'Обновлению превью'; +$_lang['ms2_utilities_import'] = 'Импорт'; +$_lang['ms2_utilities_import_debug'] = 'Режим отладки'; +$_lang['ms2_utilities_import_file_ns'] = 'Не выбран файл для импорта.'; +$_lang['ms2_utilities_import_file_ext_err'] = 'Не верное расширение файла.
Разрешен только *.csv'; +$_lang['ms2_utilities_import_file_nf'] = 'Файл с расположением [[+path]] не найден'; +$_lang['ms2_utilities_import_fields_ns'] = 'Не заполнен параметр
Поля импорта.'; +$_lang['ms2_utilities_import_intro'] = 'Простой импорт каталога товаров.
При импорте большого объема товаров ваш сайт может зависнуть.
Рекомендуем запускать выполнение в фоновом режиме, для этого необходимо отметить чекбокс "Использовать планировщик" (требуется установленный компонент Scheduler)'; +$_lang['ms2_utilities_import_key_ns'] = 'Не заполнен параметр
Уникальное поле.'; +$_lang['ms2_utilities_import_success'] = 'Импорт выполнен.
Всего обработано строк: [[+total]]
Создано: [[+created]]
Обновлено: [[+updated]]'; +$_lang['ms2_utilities_import_label_delimiter'] = 'Разделитель колонок в файле'; +$_lang['ms2_utilities_import_label_fields'] = 'Поля импорта'; +$_lang['ms2_utilities_import_label_file'] = 'Файл для импорта'; +$_lang['ms2_utilities_import_label_file_empty'] = 'Выберите файл'; +$_lang['ms2_utilities_import_required_field'] = 'Пропущено обязательное поле [[+field]]'; +$_lang['ms2_utilities_import_save_fields'] = 'Сохранить настройки полей'; +$_lang['ms2_utilities_import_save_fields_title'] = 'Сохранено'; +$_lang['ms2_utilities_import_save_fields_message'] = 'Список полей для импорта сохранен'; +$_lang['ms2_utilities_import_skip_header'] = 'Пропускать первую строку-шапку'; +$_lang['ms2_utilities_import_submit'] = 'Импортировать'; +$_lang['ms2_utilities_import_update_key'] = 'Уникальное поле для обновления'; +$_lang['ms2_utilities_import_update_products'] = 'Обновить товары'; +$_lang['ms2_utilities_import_use_scheduler'] = 'Использовать планировщик'; +$_lang['ms2_utilities_params'] = 'Параметры '; +$_lang['ms2_utilities_scheduler_nf'] = 'У вас не установлен компонент Scheduler'; +$_lang['ms2_utilities_scheduler_task_ce'] = 'Не удалось создать задание Scheduler'; +$_lang['ms2_utilities_scheduler_success'] = 'Задание Scheduler создано'; $_lang['ms2_weight'] = 'Вес'; $_lang['ms2_weight_price'] = 'Стоимость ед/вес'; $_lang['ms2_weight_price_help'] = 'Добавочная стоимость доставки за единицу веса.
Может быть использовано в кастомных классах.'; diff --git a/core/components/minishop2/lexicon/ru/setting.inc.php b/core/components/minishop2/lexicon/ru/setting.inc.php index 0a82f2312..d51c6dd44 100644 --- a/core/components/minishop2/lexicon/ru/setting.inc.php +++ b/core/components/minishop2/lexicon/ru/setting.inc.php @@ -12,6 +12,7 @@ $_lang['area_ms2_category'] = 'Категория товаров'; $_lang['area_ms2_frontend'] = 'Сайт'; $_lang['area_ms2_gallery'] = 'Галерея'; +$_lang['area_ms2_import'] = 'Импорт'; $_lang['area_ms2_main'] = 'Основные настройки'; $_lang['area_ms2_order'] = 'Заказы'; $_lang['area_ms2_payment'] = 'Платежи'; @@ -158,6 +159,8 @@ $_lang['setting_ms2_toggle_js_type_desc'] = 'если выбрано ДА будут подключены скрипты без зависимости от jQuery, написанные с использованием возможностей стандарта ES6'; $_lang['setting_ms2_use_scheduler'] = 'Использовать менеджер очередей'; $_lang['setting_ms2_use_scheduler_desc'] = 'Перед использованием убедитесь, что у вас установлен компонент Scheduler'; +$_lang['setting_ms2_utility_import_fields'] = 'Список полей для импорта'; +$_lang['setting_ms2_utility_import_fields_delimiter'] = 'Разделитель колонок в файле импорта'; $_lang['setting_ms2_vanila_js'] = 'Новые скрипты фронтенда'; $_lang['setting_ms2_vanila_js_desc'] = 'Путь к файлу инициализации новых скриптов магазина. Если хотите указать свои параметры инициализации - укажите путь к ним здесь, или очистите параметр и загрузите их вручную через шаблон сайта.'; $_lang['setting_ms2_weight_format'] = 'Формат веса'; diff --git a/core/components/minishop2/processors/mgr/utilites/gallery/update.class.php b/core/components/minishop2/processors/mgr/utilities/gallery/update.class.php similarity index 98% rename from core/components/minishop2/processors/mgr/utilites/gallery/update.class.php rename to core/components/minishop2/processors/mgr/utilities/gallery/update.class.php index d192ae470..567518974 100644 --- a/core/components/minishop2/processors/mgr/utilites/gallery/update.class.php +++ b/core/components/minishop2/processors/mgr/utilities/gallery/update.class.php @@ -65,7 +65,7 @@ public function process() } if (!is_array($products) || empty($products)) { - return $this->failure($this->modx->lexicon('ms2_utilites_gallery_err_noproducts')); + return $this->failure($this->modx->lexicon('ms2_utilities_gallery_err_noproducts')); } $i = 0; diff --git a/core/components/minishop2/processors/mgr/utilities/import/import.class.php b/core/components/minishop2/processors/mgr/utilities/import/import.class.php new file mode 100644 index 000000000..0b5687648 --- /dev/null +++ b/core/components/minishop2/processors/mgr/utilities/import/import.class.php @@ -0,0 +1,123 @@ +modx->hasPermission($this->permission)) { + return $this->modx->lexicon('access_denied'); + } + + $this->properties = $this->getProperties(); + + return parent::initialize(); + } + + + /** + * {@inheritDoc} + */ + public function getLanguageTopics() + { + return $this->languageTopics; + } + + + /** + * {@inheritDoc} + */ + public function process() + { + $required = ['importfile', 'fields', 'delimiter']; + + foreach ($required as $field) { + if (!trim($this->getProperty($field))) { + return $this->addFieldError($field, $this->modx->lexicon('field_required')); + } + } + + $importParams = [ + 'file' => $this->properties['importfile'], + 'fields' => $this->properties['fields'], + 'update' => $this->properties['update'], + 'key' => $this->properties['key'], + 'debug' => $this->properties['debug'], + 'delimiter' => $this->properties['delimiter'], + 'skip_header' => $this->properties['skip_header'], + ]; + + $scheduler = $this->getProperty('scheduler', 0); + if (empty($scheduler)) { + $importCSV = new ImportCSV($this->modx); + return $importCSV->process($importParams); + } + + + /** @var Scheduler $scheduler */ + $path = $this->modx->getOption( + 'scheduler.core_path', + null, + $this->modx->getOption('core_path') . 'components/scheduler/' + ); + $scheduler = $this->modx->getService('scheduler', 'Scheduler', $path . 'model/scheduler/'); + if (!$scheduler) { + $this->modx->log(1, 'not found Scheduler extra'); + return $this->failure($this->modx->lexicon('ms2_utilities_scheduler_nf')); + } + $task = $scheduler->getTask('minishop2', 'ms2_csv_import'); + if (!$task) { + $task = $this->createImportTask(); + } + if (empty($task)) { + return $this->failure($this->modx->lexicon('ms2_utilities_scheduler_task_ce')); + } + + $task->schedule('+1 second', $importParams); + + return $this->success($this->modx->lexicon('ms2_utilities_scheduler_success')); + } + + /** + * Creating Sheduler's task for start import + * @return false|object|null + */ + private function createImportTask() + { + $task = $this->modx->newObject('sFileTask'); + $task->fromArray([ + 'class_key' => 'sFileTask', + 'content' => '/tasks/csvImport.php', + 'namespace' => 'minishop2', + 'reference' => 'ms2_csv_import', + 'description' => 'MiniShop2 CSV import' + ]); + if (!$task->save()) { + return false; + } + return $task; + } +} + +return 'msUtilityImportProcessor'; diff --git a/core/components/minishop2/processors/mgr/utilities/import/saveconfig.class.php b/core/components/minishop2/processors/mgr/utilities/import/saveconfig.class.php new file mode 100644 index 000000000..196ce40e4 --- /dev/null +++ b/core/components/minishop2/processors/mgr/utilities/import/saveconfig.class.php @@ -0,0 +1,63 @@ +modx->hasPermission($this->permission)) { + return $this->modx->lexicon('access_denied'); + } + + return parent::initialize(); + } + + + /** + * {@inheritDoc} + */ + public function getLanguageTopics() + { + return $this->languageTopics; + } + + + /** + * {@inheritDoc} + */ + public function process() + { + $this->fields = $this->getProperty('fields'); + $this->delimiter = $this->getProperty('delimiter', ';'); + + // save fields to system settings + if ($settingFields = $this->modx->getObject('modSystemSetting', 'ms2_utility_import_fields')) { + $settingFields->set('value', $this->fields); + $settingFields->save(); + } + + // save delimiter to system settings + if ($settingDelimiter = $this->modx->getObject('modSystemSetting', 'ms2_utility_import_fields_delimiter')) { + $settingDelimiter->set('value', $this->delimiter); + $settingDelimiter->save(); + } + + return $this->success(); + } +} + +return 'msUtilityImportSaveConfigProcessor'; diff --git a/core/components/minishop2/tasks/csvImport.php b/core/components/minishop2/tasks/csvImport.php new file mode 100644 index 000000000..51e2a1e72 --- /dev/null +++ b/core/components/minishop2/tasks/csvImport.php @@ -0,0 +1,14 @@ +getOption('core_path') . 'components/minishop2/import/importCSV.php'; +$importCSV = new ImportCSV($modx); +$result = $importCSV->process($scriptProperties); + +if (!$result) { + $run->addError('csv import error'); +}