Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
35e9a9e
Integration task: collect all open PRs into one testable branch
AgelxNash Sep 6, 2026
b97a84a
PR #596: feat(order): lifecycle gate with ports, idempotency, and rol…
Ibochkarev Sep 6, 2026
cf5a080
PR #598: feat(web-api): галерея изображений товара (images[])
Ibochkarev Sep 6, 2026
226b616
PR #600: feat(web-api): нормализовать контракт ответа корзины
Ibochkarev Sep 6, 2026
5b49818
PR #614: fix: политика miniShopManagerPolicy и ACL полей заказа
Ibochkarev Sep 6, 2026
6208798
PR #619: fix(mgr): UTF-8 whitespace в combo labels model-fields
Ibochkarev Sep 6, 2026
586ca3b
PR #620: fix(mgr): сортировка секций на странице товара по sort_order
Ibochkarev Sep 6, 2026
f583aee
PR #623: feat(mgr): тип Дата для extra fields
Ibochkarev Sep 6, 2026
e482fe3
PR #624: Обнаружение рассинхрона версии пакета и файлов на диске
Ibochkarev Sep 6, 2026
78172c6
PR #627: Индивидуальный menuindex для товаров в дополнительных катего…
Ibochkarev Sep 6, 2026
43ca8b2
PR #629: fix(settings): связка способа оплаты с доставкой в менеджере
Ibochkarev Sep 6, 2026
f8ffcc5
PR #631: fix(vue): удаление из грида настроек без двойного confirm
Ibochkarev Sep 6, 2026
96d1372
PR #637: fix(web-api): CORS preflight OPTIONS до CorsMiddleware
Ibochkarev Sep 6, 2026
229dbf0
PR #639: feat(web-api): prefill order draft from customer profile aft…
Ibochkarev Sep 6, 2026
f99f60b
PR #642: fix(vue): nested category checks survive reload on product C…
Ibochkarev Sep 6, 2026
624f555
PR #643: Галерея: сортировка по имени после пакетной загрузки
Ibochkarev Sep 6, 2026
f645f1a
PR #647: chore(deps): bump postcss-selector-parser in /vueManager
dependabot[bot] Sep 6, 2026
a45919a
PR #648: chore(deps-dev): bump @humanfs/node from 0.16.6 to 0.16.8 in…
dependabot[bot] Sep 6, 2026
4e4c37c
PR #649: chore(deps): bump fast-uri from 3.1.5 to 3.1.7 in /vueManager
dependabot[bot] Sep 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 38 additions & 33 deletions _build/build.php
Original file line number Diff line number Diff line change
Expand Up @@ -471,40 +471,15 @@ private function chunks(): void
}

/**
* Add access policy
* Access policies are nested under policy templates (see policyTemplates()).
*/
private function policies(): void
{
/** @noinspection PhpIncludeInspection */
$policies = include($this->config['elements'] . 'policies.php');
if (!is_array($policies)) {
$this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not package in Access Policies');
return;
}
$attributes = [
xPDOTransport::PRESERVE_KEYS => false,
xPDOTransport::UNIQUE_KEY => ['name'],
xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['policies']),
];
foreach ($policies as $name => $data) {
if (isset($data['data'])) {
$data['data'] = json_encode($data['data']);
}
/** @var $policy modAccessPolicy */
$policy = $this->modx->newObject(modAccessPolicy::class);
$policy->fromArray(array_merge([
'name' => $name,
'lexicon' => $this->config['name_lower'] . ':permissions',
], $data)
, '', true, true);
$vehicle = $this->builder->createVehicle($policy, $attributes);
$this->builder->putVehicle($vehicle);
}
$this->modx->log(modX::LOG_LEVEL_INFO, 'Packaged in ' . count($policies) . ' Access Policies');
$this->modx->log(modX::LOG_LEVEL_INFO, 'Access policies packaged via policyTemplates()');
}

/**
* Add policy templates
* Add policy templates (and nested default policies).
*/
private function policyTemplates(): void
{
Expand All @@ -514,6 +489,11 @@ private function policyTemplates(): void
$this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not package in Policy Templates');
return;
}
/** @noinspection PhpIncludeInspection */
$policy_definitions = include($this->config['elements'] . 'policies.php');
if (!is_array($policy_definitions)) {
$policy_definitions = [];
}
$attributes = [
xPDOTransport::PRESERVE_KEYS => false,
xPDOTransport::UNIQUE_KEY => ['name'],
Expand All @@ -525,6 +505,11 @@ private function policyTemplates(): void
xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['permission']),
xPDOTransport::UNIQUE_KEY => ['template', 'name'],
],
'Policies' => [
xPDOTransport::PRESERVE_KEYS => false,
xPDOTransport::UPDATE_OBJECT => !empty($this->config['update']['policies']),
xPDOTransport::UNIQUE_KEY => ['name'],
],
],
];
foreach ($policy_templates as $name => $data) {
Expand All @@ -542,17 +527,37 @@ private function policyTemplates(): void
$permissions[] = $permission;
}
}
/** @var $permission modAccessPolicyTemplate */
$permission = $this->modx->newObject(modAccessPolicyTemplate::class);
$permission->fromArray(array_merge([
/** @var modAccessPolicyTemplate $template */
$template = $this->modx->newObject(modAccessPolicyTemplate::class);
$template->fromArray(array_merge([
'name' => $name,
'lexicon' => $this->config['name_lower'] . ':permissions',
], $data)
, '', true, true);
if (!empty($permissions)) {
$permission->addMany($permissions);
$template->addMany($permissions);
}
if ($name === 'miniShopManagerPolicyTemplate' && $policy_definitions !== []) {
$policies = [];
foreach ($policy_definitions as $policyName => $policyData) {
$payload = $policyData;
if (isset($payload['data']) && is_array($payload['data'])) {
$payload['data'] = json_encode($payload['data']);
}
/** @var modAccessPolicy $policy */
$policy = $this->modx->newObject(modAccessPolicy::class);
$policy->fromArray(array_merge([
'name' => $policyName,
'lexicon' => $this->config['name_lower'] . ':permissions',
], $payload)
, '', true, true);
$policies[] = $policy;
}
if ($policies !== []) {
$template->addMany($policies, 'Policies');
}
}
$vehicle = $this->builder->createVehicle($permission, $attributes);
$vehicle = $this->builder->createVehicle($template, $attributes);
$this->builder->putVehicle($vehicle);
}
$this->modx->log(modX::LOG_LEVEL_INFO, 'Packaged in ' . count($policy_templates) . ' Access Policy Templates');
Expand Down
27 changes: 2 additions & 25 deletions _build/elements/policies.php
Original file line number Diff line number Diff line change
@@ -1,28 +1,5 @@
<?php

$permissions = [
'mscategory_save',
'msproduct_save',
'msproduct_publish',
'msproduct_delete',
'msorder_save',
'msorder_view',
'msorder_list',
'msorder_remove',
'mssetting_save',
'mssetting_view',
'mssetting_list',
'msproductfile_save',
'msproductfile_generate',
'msproductfile_list',
];
declare(strict_types=1);

return [
'miniShopManagerPolicy' => [
'description' => 'A policy for create and update MiniShop3 categories and products.',
'parent' => 0,
'class' => '',
'lexicon' => 'minishop3:permissions',
'data' => array_fill_keys($permissions, true),
],
];
return require dirname(__DIR__, 2) . '/core/components/minishop3/config/manager_access_policy.php';
15 changes: 15 additions & 0 deletions _build/elements/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
'xtype' => 'textfield',
'area' => 'ms3_main',
],
'ms3_version' => [
'value' => '',
'xtype' => 'textfield',
'area' => 'ms3_main',
],

'ms3_category_show_nested_products' => [
'value' => true,
Expand Down Expand Up @@ -296,6 +301,16 @@
'xtype' => 'numberfield',
'area' => 'ms3_statuses',
],
'ms3_status_sent' => [
'value' => 4,
'xtype' => 'numberfield',
'area' => 'ms3_statuses',
],
'ms3_order_status_transitions' => [
'value' => '',
'xtype' => 'textfield',
'area' => 'ms3_statuses',
],
'ms3_customer_cancel_allowed_statuses' => [
'value' => '2,3',
'xtype' => 'textfield',
Expand Down
79 changes: 79 additions & 0 deletions _build/resolvers/resolver_09_policies.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

use MODX\Revolution\modAccessPolicy;
use MODX\Revolution\modAccessPolicyTemplate;
use MODX\Revolution\modX;
use xPDO\Transport\xPDOTransport;

/** @var xPDOTransport $transport */
/** @var array<string, mixed> $options */
if (!$transport->xpdo || !($transport instanceof xPDOTransport)) {
return false;
}

$modx = $transport->xpdo;
$action = $options[xPDOTransport::PACKAGE_ACTION] ?? null;
if (!in_array($action, [xPDOTransport::ACTION_INSTALL, xPDOTransport::ACTION_UPGRADE], true)) {
return true;
}

$definitionsFile = MODX_CORE_PATH . 'components/minishop3/config/manager_access_policy.php';
if (!is_readable($definitionsFile)) {
$modx->log(modX::LOG_LEVEL_ERROR, '[MiniShop3] manager_access_policy.php not found');

return false;
}

/** @var array<string, array<string, mixed>> $definitions */
$definitions = require $definitionsFile;
$template = $modx->getObject(modAccessPolicyTemplate::class, ['name' => 'miniShopManagerPolicyTemplate']);
if (!$template instanceof modAccessPolicyTemplate) {
$modx->log(modX::LOG_LEVEL_WARN, '[MiniShop3] miniShopManagerPolicyTemplate not found; skip policy repair');

return true;
}

$templateId = (int) $template->get('id');

foreach ($definitions as $name => $data) {
/** @var modAccessPolicy|null $policy */
$policy = $modx->getObject(modAccessPolicy::class, ['name' => $name]);
if ($policy instanceof modAccessPolicy) {
if ((int) $policy->get('template') !== $templateId) {
$policy->set('template', $templateId);
if (!$policy->save()) {
$modx->log(modX::LOG_LEVEL_ERROR, "[MiniShop3] Failed to link policy {$name} to template");

return false;
}
$modx->log(modX::LOG_LEVEL_INFO, "[MiniShop3] Linked existing policy {$name} to template");
}
// Existing policy data is preserved (custom site ACL overrides).
continue;
}

$payload = $data;
if (isset($payload['data']) && is_array($payload['data'])) {
$payload['data'] = json_encode($payload['data']);
}

$policy = $modx->newObject(modAccessPolicy::class);
$policy->fromArray(array_merge([
'name' => $name,
'lexicon' => 'minishop3:permissions',
'template' => $templateId,
], $payload), '', true, true);

if ($policy->save()) {
$modx->log(modX::LOG_LEVEL_INFO, "[MiniShop3] Created access policy {$name}");
continue;
}

$modx->log(modX::LOG_LEVEL_ERROR, "[MiniShop3] Failed to create access policy {$name}");

return false;
}

return true;
67 changes: 67 additions & 0 deletions _build/resolvers/resolver_09_version.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

/**
* Resolver: persist installed transport package version in ms3_version system setting.
*
* Used to detect when DB/settings were updated but component files on disk were not copied.
* Uses xPDOTransport::parseSignature (MODX vendor, always available) — no MiniShop3 src
* autoload, so this still runs after a failed file copy (#622).
*/

use MODX\Revolution\modSystemSetting;
use MODX\Revolution\modX;
use xPDO\Transport\xPDOTransport;

/** @var xPDOTransport $transport */
/** @var array $options */

if (!$transport->xpdo || !($transport instanceof xPDOTransport)) {
return true;
}

$modx = $transport->xpdo;

if (!in_array($options[xPDOTransport::PACKAGE_ACTION], [
xPDOTransport::ACTION_INSTALL,
xPDOTransport::ACTION_UPGRADE,
], true)) {
return true;
}

$packageVersion = '';
if (!empty($transport->signature)) {
[, $packageVersion] = xPDOTransport::parseSignature((string)$transport->signature);
$packageVersion = (string)$packageVersion;
}

if ($packageVersion === '') {
$modx->log(modX::LOG_LEVEL_WARN, '[MiniShop3] Could not determine package version from transport signature.');
return true;
}

/** @var modSystemSetting|null $setting */
$setting = $modx->getObject(modSystemSetting::class, ['key' => 'ms3_version']);
if (!$setting) {
$setting = $modx->newObject(modSystemSetting::class);
$setting->fromArray([
'key' => 'ms3_version',
'namespace' => 'minishop3',
'area' => 'ms3_main',
'xtype' => 'textfield',
'value' => '',
], '', true, true);
}

$setting->set('value', $packageVersion);
if (!$setting->save()) {
$modx->log(
modX::LOG_LEVEL_ERROR,
'[MiniShop3] Failed to save ms3_version system setting to ' . $packageVersion
);

return true;
}

$modx->log(modX::LOG_LEVEL_INFO, '[MiniShop3] Set ms3_version system setting to ' . $packageVersion);

return true;
10 changes: 9 additions & 1 deletion assets/components/minishop3/js/web/core/ApiClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,16 @@ class ApiClient {
*/
buildUrl (endpoint) {
const url = new URL(this.baseUrl, window.location.origin)
url.searchParams.set('route', endpoint)
const qPos = endpoint.indexOf('?')
const path = qPos === -1 ? endpoint : endpoint.slice(0, qPos)
const query = qPos === -1 ? '' : endpoint.slice(qPos + 1)
url.searchParams.set('route', path)
url.searchParams.set('ctx', this.ctx)
if (query !== '') {
new URLSearchParams(query).forEach((value, key) => {
url.searchParams.set(key, value)
})
}
return url
}

Expand Down
20 changes: 10 additions & 10 deletions assets/components/minishop3/js/web/core/CartAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
* success: true/false,
* message: "Message",
* data: {
* cart: [], // Product array
* status: {}, // Cart totals (total_cost, total_count, etc.)
* cart: {}, // Legacy map keyed by product_key (empty object, not [])
* items: [], // Always an array of line items (preferred for Nuxt)
* status: {}, // Cart totals (total_cost, total_count, total_weight, total_discount, total_positions)
* render: {} // HTML blocks for rendering (if requested)
* }
* }
Expand All @@ -35,20 +36,19 @@ class CartAPI {
*
* GET /api/v1/cart/get
*
* @param {Object} params - Additional parameters
* @param {Object} params.render - Render configuration (selectors for HTML update)
* @returns {Promise<Object>} - { success, message, data: { cart, status, render } }
* @param {Object} [params] - Query flags
* @param {boolean|number|string} [params.include_thumbs] - Opt-in item.thumb from product data
* @returns {Promise<Object>} - { success, message, data: { cart, items, status, render } }
*
* @example
* const response = await cart.get()
* console.log(response.data.cart)
* console.log(response.data.items)
* console.log(response.data.status.total_cost)
*/
async get (params = {}) {
const endpoint = '/api/v1/cart/get'

if (params.render) {
// TODO: add render parameter support in backend
let endpoint = '/api/v1/cart/get'
if (params.include_thumbs) {
endpoint += '?include_thumbs=1'
}

return this.api.get(endpoint)
Expand Down
Loading