diff --git a/_build/resolvers/resolver_02_tables.php b/_build/resolvers/resolver_02_tables.php
index 7ed7039..d5235d5 100644
--- a/_build/resolvers/resolver_02_tables.php
+++ b/_build/resolvers/resolver_02_tables.php
@@ -56,7 +56,7 @@
}
}
foreach ($tableFields as $field) {
- $manager->removeField($class, $field);
+ //$manager->removeField($class, $field);
}
// 2. Operate with indexes
$indexes = [];
diff --git a/assets/components/minishop3/css/mgr/main.css b/assets/components/minishop3/css/mgr/main.css
index 9acaf30..c91222d 100644
--- a/assets/components/minishop3/css/mgr/main.css
+++ b/assets/components/minishop3/css/mgr/main.css
@@ -89,11 +89,12 @@ a.x-menu-item .x-menu-item-text .icon { line-height: 16px; top: auto; }
.ms3-btn-action {padding: 3px 20px 3px 6px!important;}
.ms3-btn-action i.icon {padding-right: 3px;width: 15px;}
/* Windows */
-.ms3-window .x-form-label-top .x-form-item label.x-form-item-label { padding-top: 15px !important; }
+/*.ms3-window .x-form-label-top .x-form-item label.x-form-item-label { padding-top: 15px !important; }*/
.ms3-window .x-panel-mc,
.ms3-window .x-tab-panel-bwrap > .x-tab-panel-body > .x-panel { margin-top: -14px; }
.ms3-window .x-window-body.tabs { padding: 5px 10px 0 10px; }
.ms3-window .desc { font-style: italic; padding-top: 5px; color: #555555; }
+.ms3-window .text-success { color: green; }
/* Tree */
.ms3-panel .x-tree-root-ct,
.ms3-window .x-tree-root-ct { overflow: visible; }
diff --git a/assets/components/minishop3/js/mgr/utilities/extrafield/grid.js b/assets/components/minishop3/js/mgr/utilities/extrafield/grid.js
new file mode 100644
index 0000000..005abb1
--- /dev/null
+++ b/assets/components/minishop3/js/mgr/utilities/extrafield/grid.js
@@ -0,0 +1,204 @@
+ms3.grid.ExtraField = function (config) {
+ config = config || {};
+ if (!config.id) {
+ config.id = 'ms3-grid-extrafields';
+ }
+ config.disableContextMenuAction = true;
+ config.class_key = null;
+
+ Ext.applyIf(config, {
+ baseParams: {
+ action: 'MiniShop3\\Processors\\Utilities\\ExtraField\\GetList'
+ },
+ stateful: true,
+ stateId: config.id,
+ multi_select: true,
+ });
+ ms3.grid.ExtraField.superclass.constructor.call(this, config);
+};
+
+Ext.extend(ms3.grid.ExtraField, ms3.grid.Default, {
+
+ getFields: function () {
+ return ['id', 'class', 'key', 'label', 'dbtype', 'exists', 'active', 'actions'];
+ },
+
+ getTopBar: function () {
+ return [{
+ text: ' ' + _('ms3_btn_create'),
+ handler: this.createExtraField,
+ scope: this
+ }, '->', this.getSearchField()];
+ },
+
+ getColumns: function () {
+ return [
+ {
+ header: _('ms3_id'),
+ dataIndex: 'id',
+ width: 50,
+ sortable: true
+ }, {
+ header: _('ms3_extrafields_class'),
+ dataIndex: 'class',
+ width: 150,
+ sortable: true,
+ hidden: true
+ }, {
+ header: _('ms3_extrafields_key'),
+ dataIndex: 'key',
+ width: 100,
+ sortable: true
+ }, {
+ header: _('ms3_extrafields_label'),
+ dataIndex: 'label',
+ width: 100,
+ sortable: true
+ }, {
+ header: _('ms3_extrafields_dbtype'),
+ dataIndex: 'dbtype',
+ width: 100,
+ sortable: true
+ }, {
+ header: _('ms3_extrafields_exists'),
+ dataIndex: 'exists',
+ width: 80,
+ sortable: true,
+ renderer: ms3.utils.renderBoolean
+ }, {
+ header: _('ms3_active'),
+ dataIndex: 'active',
+ width: 80,
+ sortable: true,
+ renderer: ms3.utils.renderBoolean
+ }, {
+ header: _('ms3_actions'),
+ dataIndex: 'actions',
+ id: 'actions',
+ width: 70,
+ renderer: ms3.utils.renderActions
+ }
+ ];
+ },
+
+ getListeners: function () {
+ return {
+ rowDblClick: function (grid, rowIndex, e) {
+ const row = grid.store.getAt(rowIndex);
+ this.updateExtraField(grid, e, row);
+ },
+ };
+ },
+
+ extraFieldAction: function (method) {
+ const ids = this._getSelectedIds();
+ if (!ids.length) {
+ return false;
+ }
+ MODx.Ajax.request({
+ url: ms3.config.connector_url,
+ params: {
+ action: 'MiniShop3\\Processors\\Utilities\\ExtraField\\Multiple',
+ method: method,
+ ids: Ext.util.JSON.encode(ids),
+ },
+ listeners: {
+ success: {
+ fn: function () {
+ //noinspection JSUnresolvedFunction
+ this.refresh();
+ }, scope: this
+ },
+ failure: {
+ fn: function (response) {
+ MODx.msg.alert(_('error'), response.message);
+ }, scope: this
+ },
+ }
+ })
+ },
+
+ createExtraField: function (btn, e) {
+ let w = Ext.getCmp('ms3-window-extra-field-create');
+ if (w) {
+ w.close();
+ }
+ w = MODx.load({
+ xtype: 'ms3-window-extra-field-create',
+ id: 'ms3-window-extra-field-create',
+ listeners: {
+ success: {
+ fn: function () {
+ this.refresh();
+ }, scope: this
+ }
+ }
+ });
+ w.setValues({
+ attributes: '',
+ default: '',
+ class: this.config.class_key
+ });
+ w.show(e.target);
+ },
+
+ updateExtraField: function (btn, e, row) {
+ if (typeof(row) != 'undefined') {
+ this.menu.record = row.data;
+ }
+ const id = this.menu.record.id;
+
+ MODx.Ajax.request({
+ url: this.config.url,
+ params: {
+ action: 'MiniShop3\\Processors\\Utilities\\ExtraField\\Get',
+ id: id
+ },
+ listeners: {
+ success: {
+ fn: function (r) {
+ let w = Ext.getCmp('ms3-window-extra-field-update');
+ if (w) {
+ w.close();
+ }
+
+ w = MODx.load({
+ xtype: 'ms3-window-extra-field-update',
+ id: 'ms3-window-extra-field-update',
+ record: r.object,
+ listeners: {
+ success: {
+ fn: function () {
+ this.refresh();
+ }, scope: this
+ }
+ }
+ });
+ w.fp.getForm().reset();
+ w.fp.getForm().setValues(r.object);
+ w.show(e.target);
+ }, scope: this
+ }
+ }
+ });
+ },
+
+ removeExtraField: function () {
+ const ids = this._getSelectedIds();
+
+ Ext.MessageBox.confirm(
+ _('ms3_menu_remove_title'),
+ ids.length > 1
+ ? _('ms3_menu_remove_multiple_confirm')
+ : _('ms3_menu_remove_confirm'),
+ function (val) {
+ if (val == 'yes') {
+ this.extraFieldAction('Remove');
+ }
+ },
+ this
+ );
+ },
+
+});
+Ext.reg('ms3-grid-extrafields', ms3.grid.ExtraField);
diff --git a/assets/components/minishop3/js/mgr/utilities/extrafield/tree.classes.js b/assets/components/minishop3/js/mgr/utilities/extrafield/tree.classes.js
new file mode 100644
index 0000000..5fa2caf
--- /dev/null
+++ b/assets/components/minishop3/js/mgr/utilities/extrafield/tree.classes.js
@@ -0,0 +1,84 @@
+/**
+ * Generates the Extra Fiels Classes Tree
+ *
+ * @class ms3.tree.ExtraFieldClass
+ * @extends MODx.tree.Tree
+ * @param {Object} config An object of options.
+ * @xtype ms3-tree-extrafieldclass
+ */
+MODx.tree.ExtraFieldClass = function (config) {
+ config = config || {};
+ Ext.applyIf(config, {
+ title: _('user_groups'),
+ id: 'ms3-tree-extrafieldclass',
+ url: MODx.config.connector_url,
+ action: 'MiniShop3\\Processors\\Utilities\\ExtraField\\GetClassNodes',
+ rootIconCls: 'icon-group',
+ //root_id: 'n_ug_0',
+ //root_name: _('user_groups'),
+ enableDD: false,
+ rootVisible: false,
+ //ddAppendOnly: true,
+ useDefaultToolbar: false,
+ tbar: [],
+
+ resize: {
+ fn: function(cmp) {
+ if (Ext.getCmp('ms3-grid-extrafields').hidden) {
+ cmp.layout.west.getSplitBar().el.hide();
+ }
+ },
+ scope: this
+ },
+ refresh: {
+ fn: function() {
+ this.setActiveClassKey();
+ },
+ scope: this
+ }
+ });
+ MODx.tree.ExtraFieldClass.superclass.constructor.call(this, config);
+};
+
+Ext.extend(MODx.tree.ExtraFieldClass, MODx.tree.Tree, {
+ windows: {
+
+ },
+
+ /**
+ * Handles tree clicks
+ * @param {Object} n The node clicked
+ * @param {Object} e The event object
+ */
+ _handleClick: function (n, e) {
+ e.stopEvent();
+ e.preventDefault();
+
+ this.setActiveClassKey(n.attributes.type);
+
+ if (this.disableHref) { return true; }
+ if (e.ctrlKey) { return true; }
+ return true;
+ },
+ getMenu: function () {
+ return [];
+ },
+ setActiveClassKey: function(class_key) {
+ const grid = Ext.getCmp('ms3-grid-extrafields'),
+ westPanel = Ext.getCmp('ms3-tree-panel-extrafield').layout.west
+ ;
+ if (class_key) {
+ grid.store.removeAll();
+ grid.show();
+ westPanel.getSplitBar().el.show();
+ grid.config.class_key = class_key;
+ grid.store.baseParams.class = class_key;
+ grid.store.load();
+ } else {
+ grid.hide();
+ westPanel.getSplitBar().el.hide();
+
+ }
+ }
+});
+Ext.reg('ms3-tree-extrafieldclass', MODx.tree.ExtraFieldClass);
\ No newline at end of file
diff --git a/assets/components/minishop3/js/mgr/utilities/extrafield/window.js b/assets/components/minishop3/js/mgr/utilities/extrafield/window.js
new file mode 100644
index 0000000..f0ff2f2
--- /dev/null
+++ b/assets/components/minishop3/js/mgr/utilities/extrafield/window.js
@@ -0,0 +1,398 @@
+ms3.window.CreateExtraField = function (config) {
+ config = config || {};
+
+ Ext.applyIf(config, {
+ title: _('ms3_menu_create'),
+ width: 600,
+ baseParams: {
+ action: 'MiniShop3\\Processors\\Utilities\\ExtraField\\Create',
+ },
+ });
+ ms3.window.CreateExtraField.superclass.constructor.call(this, config);
+};
+
+Ext.extend(ms3.window.CreateExtraField, ms3.window.Default, {
+
+ getFields: function (config) {
+ const existsInDatabase = (config.record !== undefined) ? config.record.exists : false;
+ const existsMessage = (config.record !== undefined) ? config.record.exists_message : '';
+ return [
+ {
+ xtype: 'hidden',
+ name: 'id',
+ id: config.id + '-id'
+ }, {
+ layout: 'column',
+ items: [{
+ columnWidth: 1,
+ layout: 'form',
+ defaults: { msgTarget: 'under' },
+ items: [{
+ xtype: 'ms3-combo-combobox-default',
+ fieldLabel: _('ms3_extrafields_class'),
+ name: 'class',
+ hiddenName: 'class',
+ anchor: '99%',
+ id: config.id + '-class',
+ allowBlank: true,
+ disabled: existsInDatabase,
+ mode: 'local',
+ displayField: 'class',
+ valueField: 'class',
+ store: new Ext.data.ArrayStore({
+ id: 0,
+ fields: ['class'],
+ data: [
+ ['MiniShop3\\Model\\msProductData'],
+ ['MiniShop3\\Model\\msVendor']
+ ]
+ }),
+ }],
+ }]
+ }, {
+ layout: 'column',
+ items: [{
+ columnWidth: .33,
+ layout: 'form',
+ defaults: { msgTarget: 'under' },
+ items: [{
+ xtype: 'textfield',
+ fieldLabel: _('ms3_extrafields_key'),
+ name: 'key',
+ anchor: '99%',
+ id: config.id + '-key',
+ allowBlank: true,
+ disabled: existsInDatabase,
+ }],
+ }, {
+ columnWidth: .41,
+ layout: 'form',
+ items: [{
+ xtype: 'ms3-combo-combobox-default',
+ fieldLabel: _('ms3_extrafields_xtype'),
+ name: 'xtype',
+ hiddenName: 'xtype',
+ anchor: '99%',
+ id: config.id + '-xtype',
+ allowBlank: true,
+ editable: true,
+ forceSelection: false,
+ //disabled: existsInDatabase,
+ mode: 'local',
+ displayField: 'value',
+ valueField: 'value',
+ store: new Ext.data.ArrayStore({
+ id: 0,
+ fields: ['value'],
+ data: [
+ ['textfield'],
+ ['textarea']
+ ]
+ }),
+ }],
+ }, {
+ columnWidth: .25,
+ layout: 'form',
+ items: [{
+ xtype: 'xcheckbox',
+ fieldLabel: _('ms3_active'),
+ boxLabel: _('ms3_active'),
+ name: 'active',
+ anchor: '99%',
+ id: config.id + '-active',
+ style: { paddingTop: '10px' },
+ disabled: !existsInDatabase,
+ }],
+ }]
+ }, {
+ layout: 'column',
+ items: [{
+ columnWidth: .33,
+ layout: 'form',
+ defaults: { msgTarget: 'under' },
+ items: [{
+ xtype: 'textfield',
+ fieldLabel: _('ms3_extrafields_label'),
+ name: 'label',
+ anchor: '99%',
+ id: config.id + '-label',
+ allowBlank: true,
+ //disabled: existsInDatabase,
+ }],
+ }, {
+ columnWidth: .66,
+ layout: 'form',
+ items: [{
+ xtype: 'textfield',
+ fieldLabel: _('ms3_extrafields_description'),
+ name: 'description',
+ anchor: '99%',
+ id: config.id + '-description',
+ allowBlank: true,
+ //disabled: existsInDatabase,
+ }],
+ }]
+ }, {
+ xtype: 'displayfield',
+ cls: 'text-success',
+ html: existsMessage,
+ id: config.id + '-exists-message',
+ hidden: !existsInDatabase
+ }, {
+ id: config.id + '-create',
+ name: 'create',
+ xtype: 'hidden',
+ value: false
+ }, {
+ xtype: 'fieldset',
+ title: existsInDatabase ? _('ms3_extrafields_created') : _('ms3_extrafields_create'),
+ layout: 'column',
+ defaults: { msgTarget: 'under', border: false },
+ checkboxToggle: !existsInDatabase,
+ cls: existsInDatabase ? '' : 'x-fieldset-checkbox-toggle',
+ collapsed: !existsInDatabase,
+ listeners: {
+ expand: {
+ fn: function (p) {
+ Ext.getCmp(config.id + '-create').setValue(true);
+ Ext.getCmp(config.id + '-active').enable();
+ }, scope: this
+ },
+ collapse: {
+ fn: function (p) {
+ Ext.getCmp(config.id + '-create').setValue(false);
+ Ext.getCmp(config.id + '-active').disable();
+ Ext.getCmp(config.id + '-active').setValue(false);
+ }, scope: this
+ }
+ },
+ items: [{
+ columnWidth: 1,
+ layout: 'form',
+ items: [{
+ layout: 'column',
+ items: [{
+ columnWidth: .33,
+ layout: 'form',
+ defaults: { msgTarget: 'under' },
+ items: [{
+ xtype: 'ms3-combo-combobox-default',
+ fieldLabel: _('ms3_extrafields_dbtype'),
+ name: 'dbtype',
+ hiddenName: 'dbtype',
+ anchor: '99%',
+ id: config.id + '-dbtype',
+ allowBlank: true,
+ disabled: existsInDatabase,
+ mode: 'local',
+ displayField: 'value',
+ valueField: 'value',
+ store: new Ext.data.ArrayStore({
+ id: 0,
+ fields: ['value'],
+ // https://cheatography.com/beeftornado/cheat-sheets/mysql-5-7-data-types/
+ data: [
+ ['tinyint'],
+ ['smallint'],
+ ['mediumint'],
+ ['int'],
+ ['bigint'],
+ ['float'],
+ ['double'],
+ ['decimal'],
+ ['char'],
+ ['varchar'],
+ ['tinytext'],
+ ['text'],
+ ['mediumtext'],
+ ['longtext'],
+ ['year'],
+ ['date'],
+ ['time'],
+ ['datetime'],
+ ['timestamp']
+ ]
+ }),
+ }],
+ }, {
+ columnWidth: .33,
+ layout: 'form',
+ items: [{
+ xtype: 'textfield',
+ fieldLabel: _('ms3_extrafields_precision'),
+ name: 'precision',
+ anchor: '99%',
+ id: config.id + '-precision',
+ allowBlank: true,
+ disabled: existsInDatabase
+ }]
+ }, {
+ columnWidth: .33,
+ layout: 'form',
+ items: [{
+ xtype: 'ms3-combo-combobox-default',
+ fieldLabel: _('ms3_extrafields_phptype'),
+ name: 'phptype',
+ hiddenName: 'phptype',
+ anchor: '99%',
+ id: config.id + '-phptype',
+ allowBlank: true,
+ mode: 'local',
+ displayField: 'value',
+ valueField: 'value',
+ store: new Ext.data.ArrayStore({
+ id: 0,
+ fields: ['value'],
+ data: [
+ ['string'],
+ ['boolean'],
+ ['integer'],
+ ['float'],
+ ['json'],
+ ['datetime']
+ ]
+ })
+ }],
+ }]
+ }, {
+ layout: 'column',
+ items: [{
+ columnWidth: .33,
+ layout: 'form',
+ defaults: { msgTarget: 'under' },
+ items: [{
+ xtype: 'ms3-combo-combobox-default',
+ fieldLabel: _('ms3_extrafields_attributes'),
+ name: 'attributes',
+ hiddenName: 'attributes',
+ anchor: '99%',
+ id: config.id + '-attributes',
+ allowBlank: true,
+ disabled: existsInDatabase,
+ mode: 'local',
+ displayField: 'title',
+ valueField: 'value',
+ store: new Ext.data.ArrayStore({
+ id: 0,
+ fields: ['value', 'title'],
+ data: [
+ ['', _('no')],
+ ['BINARY', 'BINARY'],
+ ['UNSIGNED', 'UNSIGNED'],
+ ['UNSIGNED ZEROFILL', 'UNSIGNED ZEROFILL'],
+ ['on update CURRENT_TIMESTAMP', 'on update CURRENT_TIMESTAMP'],
+ ]
+ })
+ }],
+ }, {
+ columnWidth: .33,
+ layout: 'form',
+ items: [{
+ xtype: 'ms3-combo-combobox-default',
+ fieldLabel: _('ms3_extrafields_default'),
+ name: 'default',
+ hiddenName: 'default',
+ anchor: '99%',
+ id: config.id + '-default',
+ allowBlank: true,
+ disabled: existsInDatabase,
+ mode: 'local',
+ displayField: 'title',
+ valueField: 'value',
+ store: new Ext.data.ArrayStore({
+ id: 0,
+ fields: ['value', 'title'],
+ data: [
+ ['', _('no')],
+ ['NULL', 'NULL'],
+ ['CURRENT_TIMESTAMP', 'CURRENT_TIMESTAMP'],
+ ['USER_DEFINED', 'Как определено:']
+ ]
+ }),
+ listeners: {
+ afterrender: {
+ fn: function (select, rec) {
+ this.handleDefaultFields(select);
+ }, scope: this
+ },
+ select: {
+ fn: function (select, rec) {
+ this.handleDefaultFields(select);
+ }, scope: this
+ }
+ }
+ }]
+ }, {
+ columnWidth: .33,
+ layout: 'form',
+ items: [{
+ xtype: 'textfield',
+ fieldLabel: _('ms3_extrafields_default_value'),
+ name: 'default_value',
+ anchor: '99%',
+ id: config.id + '-default_value',
+ allowBlank: true,
+ disabled: existsInDatabase,
+ }],
+ }]
+ }, {
+ xtype: 'xcheckbox',
+ //fieldLabel: _('ms3_extrafields_null'),
+ boxLabel: _('ms3_extrafields_null'),
+ name: 'null',
+ anchor: '99%',
+ id: config.id + '-null',
+ allowBlank: true,
+ disabled: existsInDatabase,
+ }]
+ }]
+ }
+ ];
+ },
+
+ handleDefaultFields: function (select) {
+ const value = select.getValue();
+ let defaultValueElement = Ext.getCmp(this.config.id + '-default_value');
+ if (value === 'USER_DEFINED') {
+ defaultValueElement.show();
+ } else {
+ defaultValueElement.setValue('');
+ defaultValueElement.hide();
+ }
+ },
+
+});
+Ext.reg('ms3-window-extra-field-create', ms3.window.CreateExtraField);
+
+
+ms3.window.UpdateExtraField = function (config) {
+ config = config || {};
+
+ Ext.applyIf(config, {
+ title: _('ms3_menu_update'),
+ baseParams: {
+ action: 'MiniShop3\\Processors\\Utilities\\ExtraField\\Update',
+ }
+ });
+ ms3.window.UpdateExtraField.superclass.constructor.call(this, config);
+};
+Ext.extend(ms3.window.UpdateExtraField, ms3.window.CreateExtraField, {
+
+ getFields: function (config) {
+ const fields = ms3.window.CreateExtraField.prototype.getFields.call(this, config);
+
+ for (const i in fields) {
+ if (!fields.hasOwnProperty(i)) {
+ continue;
+ }
+ //const field = fields[i];
+ //if (field.name === 'type') {
+ // field.disabled = true;
+ //}
+ }
+
+ return fields;
+ }
+
+});
+Ext.reg('ms3-window-extra-field-update', ms3.window.UpdateExtraField);
diff --git a/assets/components/minishop3/js/mgr/utilities/panel.js b/assets/components/minishop3/js/mgr/utilities/panel.js
index 749adcc..45b648a 100644
--- a/assets/components/minishop3/js/mgr/utilities/panel.js
+++ b/assets/components/minishop3/js/mgr/utilities/panel.js
@@ -38,12 +38,71 @@ ms3.panel.Utilities = function (config) {
xtype: 'ms3-utilities-import',
cls: 'main-wrapper',
}]
+ },
+ {
+ title: _('ms3_extrafields'),
+ layout: 'form',
+ autoHeight: true,
+ items: [{
+ html: _('ms3_extrafields_intro'),
+ bodyCssClass: 'panel-desc',
+ }, {
+ layout: 'border',
+ id: 'ms3-tree-panel-extrafield',
+ height: 500,
+ flex: 0,
+ border: false,
+ defaults: {
+ border: false,
+ bodyStyle: 'background-color:transparent;'
+ }
+ , items: [
+ {
+ region: 'west',
+ cls: 'main-wrapper',
+ collapseMode: 'mini',
+ split: true,
+ useSplitTips: true,
+ monitorResize: true,
+ width: 280,
+ minWidth: 280,
+ minSize: 280,
+ maxSize: 400,
+ layout: 'fit',
+ items: [{
+ xtype: 'ms3-tree-extrafieldclass'
+ }]
+ }, {
+ region: 'center',
+ id: 'ms3-grid-extrafields',
+ xtype: 'ms3-grid-extrafields',
+ hidden: true,
+ layout: 'fit',
+ cls: 'main-wrapper'
+ }
+ ]
+ }]
}
]
}]
});
ms3.panel.Utilities.superclass.constructor.call(this, config);
+
+ Ext.getCmp('ms3-grid-extrafields').store.on('load', this.fixExtraFieldsPanelHeight);
+
};
-Ext.extend(ms3.panel.Utilities, MODx.Panel);
+Ext.extend(ms3.panel.Utilities, MODx.Panel, {
+ fixExtraFieldsPanelHeight: function () {
+ var gridExtraFields = Ext.getCmp('ms3-grid-extrafields');
+ var extraFieldsPanel = Ext.getCmp('ms3-tree-panel-extrafield');
+
+ if (gridExtraFields.rendered && extraFieldsPanel.rendered) {
+ var gridHeight = gridExtraFields.getHeight();
+ var maxHeight = (gridHeight > 500) ? gridHeight : 500;
+ extraFieldsPanel.setHeight(maxHeight);
+ Ext.getCmp('modx-content').doLayout();
+ }
+ }
+});
Ext.reg('ms3-utilities', ms3.panel.Utilities);
diff --git a/core/components/minishop3/controllers/mgr/utilities.class.php b/core/components/minishop3/controllers/mgr/utilities.class.php
index b19ee5a..b86dafa 100644
--- a/core/components/minishop3/controllers/mgr/utilities.class.php
+++ b/core/components/minishop3/controllers/mgr/utilities.class.php
@@ -31,12 +31,22 @@ public function getLanguageTopics()
*/
public function loadCustomCssJs()
{
+ $this->addCss($this->ms3->config['cssUrl'] . 'mgr/bootstrap.buttons.css');
+ $this->addCss($this->ms3->config['cssUrl'] . 'mgr/main.css');
$this->addCss($this->ms3->config['cssUrl'] . 'mgr/utilities/gallery.css');
$this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/minishop3.js');
+ $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/misc/default.grid.js');
+ $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/misc/default.window.js');
+ $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/misc/ms3.utils.js');
+ $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/misc/ms3.combo.js');
+
$this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/utilities/panel.js');
$this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/utilities/gallery/panel.js');
$this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/utilities/import/panel.js');
+ $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/utilities/extrafield/tree.classes.js');
+ $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/utilities/extrafield/grid.js');
+ $this->addJavascript($this->ms3->config['jsUrl'] . 'mgr/utilities/extrafield/window.js');
$config = $this->ms3->config;
diff --git a/core/components/minishop3/lexicon/ru/manager.inc.php b/core/components/minishop3/lexicon/ru/manager.inc.php
index 5437aa9..042655f 100644
--- a/core/components/minishop3/lexicon/ru/manager.inc.php
+++ b/core/components/minishop3/lexicon/ru/manager.inc.php
@@ -234,3 +234,22 @@
$_lang['ms3_utilities_scheduler_nf'] = 'У вас не установлен компонент Scheduler';
$_lang['ms3_utilities_scheduler_task_ce'] = 'Не удалось создать задание Scheduler';
$_lang['ms3_utilities_scheduler_success'] = 'Задание Scheduler создано';
+
+$_lang['ms3_extrafields'] = 'Расширение объектов';
+$_lang['ms3_extrafields_intro'] = 'Здесь вы можете создать дополнительные поля для объектов MiniShop3';
+$_lang['ms3_extrafields_class'] = 'Объект';
+$_lang['ms3_extrafields_key'] = 'Ключ поля';
+$_lang['ms3_extrafields_label'] = 'Заголовок';
+$_lang['ms3_extrafields_description'] = 'Описание';
+$_lang['ms3_extrafields_xtype'] = 'X-type';
+$_lang['ms3_extrafields_dbtype'] = 'Тип поля';
+$_lang['ms3_extrafields_precision'] = 'Точность';
+$_lang['ms3_extrafields_phptype'] = 'Тип поля (php)';
+$_lang['ms3_extrafields_attributes'] = 'Аттрибуты';
+$_lang['ms3_extrafields_default'] = 'По-умолчанию';
+$_lang['ms3_extrafields_default_value'] = 'Значение по-умолчанию';
+$_lang['ms3_extrafields_null'] = 'Может быть NULL';
+$_lang['ms3_extrafields_exists'] = 'Существует в БД';
+$_lang['ms3_extrafields_exists_msg'] = 'Столбец [[+column]] существует в таблице [[+table]], редактирование части полей недоступно.';
+$_lang['ms3_extrafields_create'] = 'Создать поле в БД';
+$_lang['ms3_extrafields_created'] = 'Конфигурация поля в БД';
\ No newline at end of file
diff --git a/core/components/minishop3/schema/minishop3.mysql.schema.xml b/core/components/minishop3/schema/minishop3.mysql.schema.xml
index fe7beb2..1a1ca13 100644
--- a/core/components/minishop3/schema/minishop3.mysql.schema.xml
+++ b/core/components/minishop3/schema/minishop3.mysql.schema.xml
@@ -535,4 +535,26 @@
owner="foreign"/>
+
+
\ No newline at end of file
diff --git a/core/components/minishop3/src/MiniShop3.php b/core/components/minishop3/src/MiniShop3.php
index 129876b..173f487 100644
--- a/core/components/minishop3/src/MiniShop3.php
+++ b/core/components/minishop3/src/MiniShop3.php
@@ -10,6 +10,7 @@
use MiniShop3\Controllers\Order\OrderStatus;
use MiniShop3\Controllers\Payment\PaymentInterface;
use MiniShop3\Model\msOrder;
+use MiniShop3\Utils\ExtraFields;
use MiniShop3\Utils\Format;
use MiniShop3\Utils\Plugins;
use MiniShop3\Utils\Services;
@@ -57,6 +58,9 @@ class MiniShop3
/** @var Plugins $plugins */
public $plugins;
+ /** @var ExtraFields $extraFields */
+ public $extraFields;
+
/** @var Options $options */
public $options;
@@ -107,6 +111,7 @@ public function __construct(modX $modx, array $config = [])
$this->format = new Format($this);
$this->services = new Services($this);
//$this->plugins = new Plugins($this);
+ $this->extraFields = new ExtraFields($this->modx);
$this->options = new Options($this);
$this->deleteOldDraft();
@@ -296,6 +301,7 @@ public function initialize($ctx = 'web', $scriptProperties = [])
*/
public function loadMap()
{
+ $this->extraFields->loadMap();
if (method_exists($this->pdoFetch, 'makePlaceholders')) {
// $plugins = $this->plugins->load();
// foreach ($plugins as $plugin) {
diff --git a/core/components/minishop3/src/Model/msExtraField.php b/core/components/minishop3/src/Model/msExtraField.php
new file mode 100644
index 0000000..43a056d
--- /dev/null
+++ b/core/components/minishop3/src/Model/msExtraField.php
@@ -0,0 +1,26 @@
+ 'MiniShop3\\Model',
+ 'version' => '3.0',
+ 'table' => 'ms3_extra_fields',
+ 'extends' => 'xPDO\\Om\\xPDOSimpleObject',
+ 'tableMeta' => [
+ 'engine' => 'InnoDB',
+ ],
+ 'fields' => [
+ 'class' => null,
+ 'key' => null,
+ 'label' => null,
+ 'description' => null,
+ 'xtype' => null,
+ 'dbtype' => null,
+ 'precision' => null,
+ 'phptype' => null,
+ 'null' => 0,
+ 'default' => null,
+ 'attributes' => null,
+ 'active' => 0,
+ ],
+ 'fieldMeta' => [
+ 'class' => [
+ 'dbtype' => 'varchar',
+ 'precision' => '191',
+ 'phptype' => 'string',
+ 'null' => true,
+ ],
+ 'key' => [
+ 'dbtype' => 'varchar',
+ 'precision' => '64',
+ 'phptype' => 'string',
+ 'null' => true,
+ ],
+ 'label' => [
+ 'dbtype' => 'varchar',
+ 'precision' => '191',
+ 'phptype' => 'string',
+ 'null' => true,
+ ],
+ 'description' => [
+ 'dbtype' => 'varchar',
+ 'precision' => '191',
+ 'phptype' => 'string',
+ 'null' => true,
+ ],
+ 'xtype' => [
+ 'dbtype' => 'varchar',
+ 'precision' => '191',
+ 'phptype' => 'string',
+ 'null' => true,
+ ],
+ 'dbtype' => [
+ 'dbtype' => 'varchar',
+ 'precision' => '100',
+ 'phptype' => 'string',
+ 'null' => true,
+ ],
+ 'precision' => [
+ 'dbtype' => 'varchar',
+ 'precision' => '100',
+ 'phptype' => 'string',
+ 'null' => true,
+ ],
+ 'phptype' => [
+ 'dbtype' => 'varchar',
+ 'precision' => '100',
+ 'phptype' => 'string',
+ 'null' => true,
+ ],
+ 'null' => [
+ 'dbtype' => 'tinyint',
+ 'precision' => '1',
+ 'phptype' => 'boolean',
+ 'null' => true,
+ 'default' => 1,
+ ],
+ 'default' => [
+ 'dbtype' => 'varchar',
+ 'precision' => '191',
+ 'phptype' => 'string',
+ 'null' => true,
+ ],
+ 'attributes' => [
+ 'dbtype' => 'varchar',
+ 'precision' => '191',
+ 'phptype' => 'string',
+ 'null' => true,
+ ],
+ 'active' => [
+ 'dbtype' => 'tinyint',
+ 'precision' => '1',
+ 'phptype' => 'boolean',
+ 'null' => true,
+ 'default' => 0,
+ ]
+ ],
+ 'indexes' => [],
+ 'composites' => [],
+ ];
+}
diff --git a/core/components/minishop3/src/Processors/Utilities/ExtraField/Create.php b/core/components/minishop3/src/Processors/Utilities/ExtraField/Create.php
new file mode 100644
index 0000000..4bec81d
--- /dev/null
+++ b/core/components/minishop3/src/Processors/Utilities/ExtraField/Create.php
@@ -0,0 +1,106 @@
+modx->hasPermission($this->permission)) {
+ return $this->modx->lexicon('access_denied');
+ }
+
+ $this->extraFields = new ExtraFields($this->modx);
+
+ return parent::initialize();
+ }
+
+
+ /**
+ * @return bool
+ */
+ public function beforeSet()
+ {
+ $this->createColumn = filter_var($this->getProperty('create'), FILTER_VALIDATE_BOOLEAN);
+
+ $required = ['class', 'key'];
+ if ($this->createColumn) {
+ $required = array_merge($required, ['dbtype', 'phptype']);
+ }
+ foreach ($required as $field) {
+ if (!$tmp = trim($this->getProperty($field))) {
+ $this->addFieldError($field, $this->modx->lexicon('field_required'));
+ } else {
+ $this->setProperty($field, $tmp);
+ }
+ }
+
+ if ($this->hasErrors()) {
+ return false;
+ }
+
+ $className = $this->getProperty('class');
+ $key = $this->getProperty('key');
+ $classFields = $this->modx->getFields($className);
+ if (!empty($classFields)) {
+ if (array_key_exists($key, $classFields)) {
+ $this->addFieldError('key', $this->modx->lexicon('ms3_err_ae'));
+ }
+ }
+
+ $doesAlreasyExistCriteria = [
+ 'class' => $this->getProperty('class'),
+ 'key' => $this->getProperty('key')
+ ];
+ if ($this->doesAlreadyExist($doesAlreasyExistCriteria)) {
+ $this->modx->error->addField('key', $this->modx->lexicon('ms3_err_ae'));
+ }
+
+ return !$this->hasErrors();
+ }
+
+ /**
+ * @return array
+ */
+ public function beforeSave()
+ {
+ if ($this->createColumn) {
+
+ $class = $this->object->get('class');
+ $key = $this->object->get('key');
+ // TODO: Не однозначное поведение, если в базе существует столбец, а пользователь создаст с другим dbtype
+ if (!$this->extraFields->columnExists($class, $key)) {
+ if (!$this->extraFields->addColumn($this->object)) {
+ // TODO: заменить текст ошибки на "Ошибка добавления поля"
+ $this->modx->error->addField('key', $this->modx->lexicon('ms3_err_unknown'));
+ }
+ }
+ }
+
+ return parent::beforeSave();
+ }
+
+ public function afterSave()
+ {
+ $this->extraFields->clearCache();
+ return parent::afterSave();
+ }
+}
diff --git a/core/components/minishop3/src/Processors/Utilities/ExtraField/Get.php b/core/components/minishop3/src/Processors/Utilities/ExtraField/Get.php
new file mode 100644
index 0000000..e5c4edf
--- /dev/null
+++ b/core/components/minishop3/src/Processors/Utilities/ExtraField/Get.php
@@ -0,0 +1,52 @@
+modx->hasPermission($this->permission)) {
+ return $this->modx->lexicon('access_denied');
+ }
+
+ return parent::initialize();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return mixed
+ */
+ public function beforeOutput()
+ {
+ $extraFieldsManager = new ExtraFields($this->modx);
+
+ $className = $this->object->get('class');
+ $columnName = $this->object->get('key');
+ $table = $this->modx->getTableName($className);
+ $exists = $extraFieldsManager->columnExists($className, $columnName);
+
+ $this->object->set('exists', $exists);
+ $existsMessage = '';
+ if ($exists) {
+ $existsMessage = $this->modx->lexicon('ms3_extrafields_exists_msg', ['column' => $columnName, 'table' =>$table]);
+ }
+ $this->object->set('exists_message', $existsMessage);
+
+ parent::beforeOutput();
+ }
+}
diff --git a/core/components/minishop3/src/Processors/Utilities/ExtraField/GetClassNodes.php b/core/components/minishop3/src/Processors/Utilities/ExtraField/GetClassNodes.php
new file mode 100644
index 0000000..fa4660f
--- /dev/null
+++ b/core/components/minishop3/src/Processors/Utilities/ExtraField/GetClassNodes.php
@@ -0,0 +1,115 @@
+modx->hasPermission($this->permission);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return array
+ */
+ public function getLanguageTopics()
+ {
+ return $this->languageTopics;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return mixed
+ */
+ public function initialize()
+ {
+ $this->setDefaultProperties([
+ 'id' => 0,
+ 'sort' => 'name',
+ 'dir' => 'ASC',
+ 'showAnonymous' => true,
+ ]);
+
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return mixed
+ */
+ public function process()
+ {
+ $classes = [];
+ if ($this->getProperty('id') == 'root') {
+ $classes = $this->getClasses();
+ }
+
+ $list = [];
+ /** @var modUserGroup $group */
+ foreach ($classes as $class => $props) {
+ $list[] = $this->prepareClass($class, $props);
+ }
+
+ return $this->toJSON($list);
+ }
+
+ /**
+ * Get the User Groups within the filter
+ * @return array
+ */
+ public function getClasses()
+ {
+ return [
+ msProductData::class => [
+ //'text' => $this->modx->lexicon('ms3_product') . ' (msProductData)',
+ 'iconCls' => $this->modx->getOption('mgr_tree_icon_msproduct', null, 'icon icon-tag')
+ ],
+ msVendor::class => [
+ 'iconCls' => 'icon icon-industry'
+ ],
+ //msOrder::class => [
+ //],
+ ];
+ }
+
+ /**
+ * Prepare a User Group for listing
+ * @param modUserGroup $group
+ * @return array
+ */
+ public function prepareClass(string $classKey, array $props)
+ {
+ $cls = '';
+
+ $path = explode('\\', $classKey);
+ $shortClass = array_pop($path);
+
+ $itemArray = array_merge([
+ 'text' => htmlentities($shortClass),
+ 'id' => 'n_' . str_replace('\\', '__', $classKey),
+ 'hasChildren' => false,
+ 'expanded' => false,
+ 'childCount' => 0,
+ 'type' => $classKey,
+ 'qtip' => $classKey,
+ 'cls' => $cls,
+ 'allowDrop' => false,
+ 'iconCls' => 'icon icon-dice-d6',
+ ], $props);
+
+ return $itemArray;
+ }
+}
diff --git a/core/components/minishop3/src/Processors/Utilities/ExtraField/GetList.php b/core/components/minishop3/src/Processors/Utilities/ExtraField/GetList.php
new file mode 100644
index 0000000..5f7574d
--- /dev/null
+++ b/core/components/minishop3/src/Processors/Utilities/ExtraField/GetList.php
@@ -0,0 +1,107 @@
+extraFieldsManager = new ExtraFields($this->modx);
+
+ return parent::initialize();
+ }
+
+ /**
+ * @param xPDOQuery $c
+ *
+ * @return xPDOQuery
+ */
+ public function prepareQueryBeforeCount(xPDOQuery $c)
+ {
+ if ($this->getProperty('combo')) {
+ $c->select('id,key');
+ }
+ if ($id = (int)$this->getProperty('id')) {
+ $c->where(['id' => $id]);
+ }
+
+ if ($class = trim($this->getProperty('class'))) {
+ $c->where(['class' => $class]);
+ }
+
+ if ($query = trim($this->getProperty('query'))) {
+ $c->where([
+ 'key:LIKE' => "%{$query}%",
+ 'OR:label:LIKE' => "%{$query}%",
+ ]);
+ }
+
+ return $c;
+ }
+
+
+ /**
+ * @param xPDOObject $object
+ *
+ * @return array
+ */
+ public function prepareRow(xPDOObject $object)
+ {
+ if ($this->getProperty('combo')) {
+ $data = [
+ 'id' => $object->get('id'),
+ 'key' => $object->get('key'),
+ ];
+ } else {
+ $data = $object->toArray();
+ $data['exists'] = $this->extraFieldsManager->columnExists($data['class'], $data['key']);
+ $data['dbtype'] = empty($data['precision'])
+ ? $data['dbtype']
+ : sprintf("%s (%s)", $data['dbtype'], $data['precision']);
+
+ $data['actions'] = [];
+
+ $data['actions'][] = [
+ 'cls' => '',
+ 'icon' => 'icon icon-edit',
+ 'title' => $this->modx->lexicon('ms3_menu_update'),
+ 'action' => 'updateExtraField',
+ 'button' => true,
+ 'menu' => true,
+ ];
+
+ $data['actions'][] = [
+ 'cls' => [
+ 'menu' => 'red',
+ 'button' => 'red',
+ ],
+ 'icon' => 'icon icon-trash-o',
+ 'title' => $this->modx->lexicon('ms3_menu_remove'),
+ 'multiple' => $this->modx->lexicon('ms3_menu_remove_multiple'),
+ 'action' => 'removeExtraField',
+ 'button' => true,
+ 'menu' => true,
+ ];
+ }
+
+ return $data;
+ }
+}
diff --git a/core/components/minishop3/src/Processors/Utilities/ExtraField/Multiple.php b/core/components/minishop3/src/Processors/Utilities/ExtraField/Multiple.php
new file mode 100644
index 0000000..5c299a4
--- /dev/null
+++ b/core/components/minishop3/src/Processors/Utilities/ExtraField/Multiple.php
@@ -0,0 +1,34 @@
+getProperty('method', false);
+ if (!$method) {
+ return $this->failure();
+ }
+ $method = ucfirst($method);
+ $ids = json_decode($this->getProperty('ids'), true);
+ if (empty($ids)) {
+ return $this->success();
+ }
+
+ foreach ($ids as $id) {
+ $this->modx->runProcessor('MiniShop3\\Processors\\Utilities\\ExtraField\\' . $method, [
+ 'id' => $id,
+ ]);
+ }
+
+ return $this->success();
+ }
+}
diff --git a/core/components/minishop3/src/Processors/Utilities/ExtraField/Remove.php b/core/components/minishop3/src/Processors/Utilities/ExtraField/Remove.php
new file mode 100644
index 0000000..655aa9c
--- /dev/null
+++ b/core/components/minishop3/src/Processors/Utilities/ExtraField/Remove.php
@@ -0,0 +1,35 @@
+modx->hasPermission($this->permission)) {
+ return $this->modx->lexicon('access_denied');
+ }
+
+ return parent::initialize();
+ }
+
+ public function afterRemove()
+ {
+ $extraFields = new ExtraFields($this->modx);
+ $extraFields->clearCache();
+
+ return parent::afterRemove();
+ }
+}
diff --git a/core/components/minishop3/src/Processors/Utilities/ExtraField/Update.php b/core/components/minishop3/src/Processors/Utilities/ExtraField/Update.php
new file mode 100644
index 0000000..20fd696
--- /dev/null
+++ b/core/components/minishop3/src/Processors/Utilities/ExtraField/Update.php
@@ -0,0 +1,145 @@
+modx->hasPermission($this->permission)) {
+ return $this->modx->lexicon('access_denied');
+ }
+
+ $this->extraFields = new ExtraFields($this->modx);
+
+ return parent::initialize();
+ }
+
+ /**
+ * @return bool
+ */
+ public function beforeSet()
+ {
+ $existsInDb = $this->extraFields->columnExists($this->object->get('class'), $this->object->get('key'));
+ if ($existsInDb) {
+ $this->createColumn = false;
+ $required = [];
+ // We cannot change these fields if the field exists in the database
+ $this->unsetProperty('class');
+ $this->unsetProperty('key');
+ $this->unsetProperty('dbtype');
+ $this->unsetProperty('precision');
+ $this->unsetProperty('null');
+ $this->unsetProperty('default');
+ $this->unsetProperty('default_value');
+ $this->unsetProperty('attributes');
+ } else {
+ $this->createColumn = filter_var($this->getProperty('create'), FILTER_VALIDATE_BOOLEAN);
+ $required = ['class', 'key'];
+ if ($this->createColumn) {
+ $required = array_merge($required, ['dbtype', 'phptype']);
+ }
+ }
+
+ foreach ($required as $field) {
+ if (!$tmp = trim($this->getProperty($field))) {
+ $this->addFieldError($field, $this->modx->lexicon('field_required'));
+ } else {
+ $this->setProperty($field, $tmp);
+ }
+ }
+
+ if ($this->hasErrors()) {
+ return false;
+ }
+
+ if (!$existsInDb) {
+ $class = $this->getProperty('class');
+ $key = $this->getProperty('key');
+ $doesAlreasyExistCriteria = [
+ 'id:!=' => $this->object->get('id'),
+ 'class' => $class,
+ 'key' => $key
+ ];
+ if ($this->doesAlreadyExist($doesAlreasyExistCriteria) || $this->doesBuiltIn($class, $key)) {
+ $this->modx->error->addField('key', $this->modx->lexicon('ms3_err_ae'));
+ }
+ }
+
+ return !$this->hasErrors();
+ }
+
+ /**
+ * @return array
+ */
+ public function beforeSave()
+ {
+ if ($this->createColumn) {
+ $class = $this->object->get('class');
+ $key = $this->object->get('key');
+ // TODO: Не однозначное поведение, если в базе существует столбец, а пользователь создаст с другим dbtype
+ if (!$this->extraFields->columnExists($class, $key)) {
+ if (!$this->extraFields->addColumn($this->object)) {
+ // TODO: заменить текст ошибки на "Ошибка добавления поля"
+ $this->modx->error->addField('key', $this->modx->lexicon('ms3_err_unknown'));
+ }
+ }
+ }
+
+ return parent::beforeSave();
+ }
+
+ public function afterSave()
+ {
+ $this->extraFields->clearCache();
+ return parent::afterSave();
+ }
+
+
+ /**
+ * Checks if the specified field is built-in.
+ *
+ * @param string $key
+ * @return boolean
+ */
+ private function doesBuiltIn(string $className, string $fieldName): bool
+ {
+ $builtInFields = [
+ msProductData::class => [
+ 'id', 'article', 'price', 'old_price', 'weight', 'image', 'thumb', 'vendor_id',
+ 'made_in', 'new', 'popular', 'favorite', 'tags', 'color', 'size', 'source_id'
+ ],
+ msVendor::class => [
+ 'id', 'position', 'name', 'resource_id', 'country', 'logo', 'address', 'phone', 'email',
+ 'description', 'properties'
+ ]
+ ];
+ if (!array_key_exists($className, $builtInFields)) {
+ $this->modx->log(xPDO::LOG_LEVEL_ERROR, 'The specified class is not supported: ' . $className);
+ return false;
+ }
+ $normalizedFieldName = strtolower(trim($fieldName));
+ return in_array($normalizedFieldName, $builtInFields[$className]);
+ }
+}
diff --git a/core/components/minishop3/src/Utils/ExtraFields.php b/core/components/minishop3/src/Utils/ExtraFields.php
new file mode 100644
index 0000000..e57b9bb
--- /dev/null
+++ b/core/components/minishop3/src/Utils/ExtraFields.php
@@ -0,0 +1,168 @@
+ 'ms3'];
+
+ public function __construct(modX $modx)
+ {
+ $this->modx = $modx;
+ }
+
+ /**
+ * Adds extra fields to the xPDO map
+ *
+ * @return void
+ */
+ public function loadMap()
+ {
+ if (!$fieldsMeta = $this->modx->cacheManager->get($this->cacheKey, $this->cacheOptions)) {
+ $fieldsMeta = [];
+ $c = $this->modx->newQuery(msExtraField::class, ['active' => 1]);
+ $fields = $this->modx->getIterator(msExtraField::class, $c);
+ foreach ($fields as $field) {
+ $fieldsMeta[] = $this->getFieldInfo($field);
+ }
+ $this->modx->cacheManager->add($this->cacheKey, $fieldsMeta, 0, $this->cacheOptions);
+ }
+
+ foreach ($fieldsMeta as $fieldMeta) {
+ $this->addFieldToMap($fieldMeta);
+ }
+ }
+
+ /**
+ * Clears cache
+ *
+ * @return void
+ */
+ public function clearCache()
+ {
+ // TODO: [apha] Проверить, что при общей очистке кеша MODx этот метод сработает
+ $this->modx->cacheManager->delete($this->cacheKey, $this->cacheOptions);
+ }
+
+ /**
+ * Checks that a column exists in a table
+ *
+ * @param string $class
+ * @param string $columnName
+ * @return boolean
+ */
+ public function columnExists(string $class, string $columnName): bool
+ {
+ if (!empty($class)) {
+ try {
+ $tableName = $this->modx->getTableName($class);
+ if ($tableName) {
+ $stmt = $this->modx->prepare("SHOW COLUMNS FROM {$tableName} LIKE ?");
+ $stmt->execute([$columnName]);
+ $result = $stmt->fetch(PDO::FETCH_ASSOC);
+ if ($result) {
+ return true;
+ }
+ }
+ } catch (PDOException $e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Adds a column to a table
+ *
+ * @param msExtraField $msExtraField
+ * @return boolean
+ */
+ public function addColumn(msExtraField $msExtraField): bool
+ {
+ $meta = $this->getFieldInfo($msExtraField);
+ if ($meta) {
+ $this->addFieldToMap($meta);
+
+ $xpdoManager = $this->modx->getManager();
+ return $xpdoManager->addField($msExtraField->get('class'), $msExtraField->get('key'));
+ }
+
+ return false;
+ }
+
+ /**
+ * Adds a field to the xPDO map
+ *
+ * @param array $meta
+ * @return void
+ */
+ private function addFieldToMap(array $meta)
+ {
+ $class = $meta['class'];
+ $field = $meta['field'];
+
+ $classMap = $this->modx->map[$class];
+ $classMap['fields'][$field] = $meta['default'];
+ $classMap['fieldMeta'][$field] = $meta['meta'];
+
+ $this->modx->map[$class] = $classMap;
+ }
+
+ /**
+ * Prepares field info with metadata
+ *
+ * @param msExtraField $field
+ * @return null|array
+ */
+ private function getFieldInfo(msExtraField $field): mixed
+ {
+ if ($field == null || !($field instanceof msExtraField)) {
+ return null;
+ }
+
+ foreach (['class', 'key', 'dbtype', 'phptype'] as $required) {
+ if (empty($field->get($required))) {
+ return null;
+ }
+ }
+
+ $meta = [];
+ foreach (['dbtype', 'phptype', 'null', 'precision', 'attributes'] as $k) {
+ $v = $field->get($k);
+ if (!empty($v)) {
+ $meta[$k] = $v;
+ }
+ }
+
+ $default = $field->get('default');
+ switch ($default) {
+ case 'NULL':
+ $meta['default'] = null;
+ break;
+ case 'CURRENT_TIMESTAMP':
+ $meta['default'] = 'CURRENT_TIMESTAMP';
+ break;
+ case 'USER_DEFINED':
+ $meta['default'] = $field->get('default_value');
+ break;
+ default:
+ break;
+ }
+
+ return [
+ 'class' => $field->get('class'),
+ 'field' => $field->get('key'),
+ 'default' => array_key_exists('default', $meta) ? $meta['default'] : null,
+ 'meta' => $meta
+ ];
+ }
+}