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');
+}