diff --git a/.gitignore b/.gitignore
index 5be2119..ea85efc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,9 +5,11 @@
/.vscode/
/.claude/
/.agents/
+/.playwright-mcp/
# Node dependencies
/node_modules/
+/modules/mcp/transport/node_modules/
# Composer dev dependencies (тесты). composer.json/composer.lock — в гите.
/vendor/
@@ -20,9 +22,13 @@
/tmp/
/uploads/
/storage/backups/
+/storage/importprice/
+/storage/imports/
+/storage/logs/
/storage/releases/
/storage/runtime/
/storage/secrets/
+/storage/tmp/
/storage/updates/
/storage/installed.lock
/sample/
diff --git a/PACKAGE-MANIFEST.json b/PACKAGE-MANIFEST.json
index 4906586..5a817cc 100644
--- a/PACKAGE-MANIFEST.json
+++ b/PACKAGE-MANIFEST.json
@@ -2,31 +2,33 @@
"format": "ave-core-package-v2",
"product": "AVE.cms",
"version": "3.3",
- "build": "0.32",
+ "build": "0.35",
"package": "core",
- "built_at": "2026-07-27T12:48:15+00:00",
+ "built_at": "2026-07-30T08:43:49+00:00",
"installed_modules": 0,
"bundled_optional_modules": [],
"optional_after_install": [
"setup/"
],
- "core_fingerprint": "7e65e16538e861c03bc15322b9642d07f75830abec0a852da54ce22431887dc5",
+ "core_fingerprint": "713dd207dba2d9f7242b30de3fd39a08a2b523cabd615a69873c3b3c48a133ea",
"files": {
- ".gitignore": "e2c156d23abc1be6e5a61c9478bb3f1c2b31eea9c7ff2f5e8bd768745638bd93",
+ ".gitignore": "9aa78fb183d01611e3ce9789d244d09ffb862c6ae718438b2132a7edd53a16ec",
".htaccess": "71c06b7bc41b09c347a562ca72768acaa0ee1ef8388c749fc67956c793d8a826",
- "README.md": "9660a68a4a9f99436a32e219d3cabd5b373caecd7b41404a637d95a4626cb464",
+ "README.md": "7d3f3a226250482eb09aab8188b88c728145a632d7cafe6d742b6003a0ca4d8c",
"adminx/.htaccess": "d03ea2d2972af8b7545a2d57c6253f303d15adeb30b775e161d887ca5ff73378",
"adminx/Support/AdminLocale.php": "a7fe84d86d65c9f021106172b1789c8bc6221c36b2fe39cf883ca9a505e3e8ad",
+ "adminx/Support/BulkActionExecutor.php": "82859d10eec39a53091ed46604d6819e7111b7d879e5f0f1595c26f8a7ff1a60",
"adminx/Support/CodeEditor.php": "51532980b1990e595b796d884c1a8bcc46f77f88344386e5153b4bef4cae973c",
- "adminx/Support/InterfaceSettings.php": "43280d104e731781213f7af119ed21130e8b29e103fbccb77ed01a1ca7441b8f",
- "adminx/Support/ModuleExtensions.php": "e1a0407638b16a773bfe414c214015aef5bcd6cb4cc6fdf772c1fd1ff43ad086",
+ "adminx/Support/GlobalSearchRegistry.php": "7722b193cd60161a29d8fd166c4445294528eac5336b2810a1d9c281347a188e",
+ "adminx/Support/InterfaceSettings.php": "e42cdc1157d8a55760278ab46d603316039fad0680eadf7c95824984f45f63ea",
+ "adminx/Support/ModuleExtensions.php": "36de2286496c8327ee22d806f98a6bc9521a2d81cd1bb4182122c7925ed8d0a7",
"adminx/Support/Notifications.php": "ab08805d571cd3d8a4b2a09fdc6ce96fb4c7b5ec75691322cf8a4e1a142112a3",
"adminx/Support/PhpCode.php": "5cdd56c7deddd713dd06412b64da729c8a411aa99f46958fc29121055740cd07",
"adminx/Support/ReAuth.php": "f8c2146fb4b8f03b7d0cd2f3576e3f39fe2749823c5d6f1dca494268dcaba303",
"adminx/Support/Roles.php": "6be335dd643317f77bd7c2db92f25895e601fe72818befd62f6aeb3ecac9fce7",
"adminx/Support/RouteGuard.php": "e9e20c097c0db354851546585be72bfc71a5d14652871f8bd1160e845d4dab1b",
"adminx/Support/SavedViews.php": "4548ca129f270184e61cb670e7f44a1c103099a6a6c2820bed1a11bc18f227c4",
- "adminx/Support/SectionHelp.php": "c12ff274abc7f508d7ec28f02c770acdedb62390e1661914ecf740a3ea8b62f8",
+ "adminx/Support/SectionHelp.php": "75388bc96c0b43aad13befdf22884401b05361fc1d6dd5d0f1e85e00c09d2bdb",
"adminx/Support/SensitiveRoutes.php": "8d40c429d6abbc1416e3797908672e551d2b3abe64cb1763c6c410e7edb9dbce",
"adminx/Support/SystemFeatures.php": "f18ada623710f8c3efc6588c037227f6074e75bad9a3820aeef60b4d8d4b20a4",
"adminx/Support/SystemHealth.php": "286aee452e90f71ad64437bd39d6dd985d312eb85bbb711490d3a0b631a16aea",
@@ -34,7 +36,7 @@
"adminx/Support/Twig/AdminLocaleExtension.php": "1fb6c1d1c35d74fdb4a578cd15b3f19441725db6f11b336dc6948ed982842f3a",
"adminx/Support/Twig/AdminLocaleNodeVisitor.php": "ccf387680fe8b0e11bd0fe0d23769be8b7ed9afc363cb9a802db5c4aeb7d405b",
"adminx/Support/Twig/AdminTextNode.php": "e51d8d3ee49411f42c8a874cd283f60c1fa0af6506b34568a119ebe2d18d7268",
- "adminx/assets/css/adminkit.css": "4c8371e04dd1d31058f9a17e4ab741665e94aac388d7307b8115ca613c562c5f",
+ "adminx/assets/css/adminkit.css": "15409efc9387120866479c3351a4a291ee7d8207694a84b228fc5d35fc184cbe",
"adminx/assets/fonts/NunitoSans-Bold.woff2": "2d48580b7e1be60fa94b8092ebe86471fe0adfe5ca75242a7af2042ac9da825f",
"adminx/assets/fonts/NunitoSans-ExtraBold.woff2": "66612ee9d1cdc341c9eec8a43104bbf515e7cc52821558bfc7dfcf33c8cb0cc1",
"adminx/assets/fonts/NunitoSans-Regular.woff2": "878adc25278872933f004783f0ebbe092cebb3f9161aa30cffb2b9a8a07db040",
@@ -42,12 +44,16 @@
"adminx/assets/fonts/OpenSans-Regular.woff2": "a26ed465673123933c5510730a7286f49016a40fa94062021d1dc4c9bfeddbd3",
"adminx/assets/fonts/OpenSans-SemiBold.woff2": "7d963e1d2f61c11c2e8bd2eb528777b5a55569708d50a7ee7bd097d7fafe6da4",
"adminx/assets/img/logo.svg": "685d9bf037b33fbcfc200947d5051c5325762f8ab8a29ac9e1e587076321e463",
- "adminx/assets/js/adminx.js": "dbcfc5ea3918f551e7d1b9dfaeca6f2637b763403434e25d4d17cc60bbe91162",
+ "adminx/assets/js/adminx.js": "be65a81e87bb6580c0f3b59514c92627611ad18ba62f090a1600ed5fff20b63e",
+ "adminx/assets/js/bulk-actions.js": "d7ee80678e6703aff2418a4679ba8e5027e7220cb089848bcced6d9f100b3aae",
+ "adminx/assets/js/command-palette.js": "f62f0bad5ddf2b6e813e7eda094d73df63dce09a6e581025971e8f10430e8d9f",
"adminx/assets/js/editor-codemirror.js": "90a04c219e84f5391a40b0d1fc807ecfe4ca582a198bc202bdc5aa6dccec51fa",
- "adminx/assets/js/editor-tiptap.js": "9bf4fd26e441bb5e53aa73ec68fc8cd481399d0660446cc6e27cf1521711092c",
- "adminx/assets/js/media-picker.js": "cc2374621634fb4430c066f0318cee326becf724cb4a5f9810b325740a903ed3",
+ "adminx/assets/js/editor-tiptap.js": "b924d1deccda0e85d947ec936ead0d32f85aa5517893d197316f75a614ac5092",
+ "adminx/assets/js/media-picker.js": "eec036a03bd1d0d7995708a15f3337cc6e5d2e8ede7d5be3b0c5eeaeed782ebc",
"adminx/assets/js/saved-views.js": "30c02121634f6f035dcff6a730e6fe300a1892fc9321971f142dcff600802f30",
- "adminx/assets/src/editor-tiptap.js": "5639dd0d6f844aa4066fa89833b4e7f63ffda2b7bbca40e3edef8959af02bf57",
+ "adminx/assets/src/bulk-actions.js": "29bcc568366ce4a86a71b12a0f8be050bd71a9eabb0f65af6d9ca57281db9201",
+ "adminx/assets/src/command-palette.js": "ff86b0dc0f1ad59d551af772643dbe094cdfb2367d402eca49fd1a4c5b1bb9db",
+ "adminx/assets/src/editor-tiptap.js": "e4cf6d82b1759708e9e2df5bb54136570e572a99138cd57f6eee7f91257ed7c4",
"adminx/assets/vendor/codemirror/addon/dialog/dialog.css": "5df690d771f12118348562dd2a90060433ae2edeccd0908abcaa4480b1e3e468",
"adminx/assets/vendor/codemirror/addon/dialog/dialog.js": "1eea0e500dce10d8594d8d681a91d1b71a4209355c9c29885e86e26b2d76685e",
"adminx/assets/vendor/codemirror/addon/edit/closetag.js": "700b8aa7c46e040287e32ffd1edb86a42d09edc5b2a9594241590b154c0232bd",
@@ -132,17 +138,17 @@
"adminx/assets/vendor/codemirror/theme/zenburn.css": "47780bcc6d429d434392aed5b63c26efed6bce3d34df1158573700ee189eab2f",
"adminx/assets/vendor/tabler/fonts/tabler-icons.woff2": "cce741fcb6c526dfea3c0d48ff486279ac723617cd5b4de2bd5d726e94cef477",
"adminx/assets/vendor/tabler/tabler-icons.min.css": "676509f34c06d34b75e4227e4c36105e5d220b458f609fffec247f20a8d72a8d",
- "adminx/core-permissions.php": "9ec2e97e40c625f1878249232db60569747098dfb6c81fc0a3654d09807e6daa",
- "adminx/index.php": "493cb39959a6b58903bd7dd470930b8f69ed859cf596cda1f8076b08579e97b4",
- "adminx/language/en/client.xml": "f80a1538b75c0a727db745f21dac354c99693f13a952a26e36d7fe2687a54e59",
+ "adminx/core-permissions.php": "084fbff2f7625f0e1b6a6a15d00fce536212adbdf40f6a7fa2eef34cab54394b",
+ "adminx/index.php": "212b324ada416cff2da4eb876976411eab53890a7ef60784136ffdf4fa60decf",
+ "adminx/language/en/client.xml": "de712c66a939fcae607c087e0941dfbf824152a41be85aeae0a0406cc0ef3053",
"adminx/language/en/common.xml": "e5f9972786f1d2a8e05f86913b07c361b82811f99cf51bee6d38e9ed275fc995",
"adminx/language/en/interface.xml": "80ccb298e1ae8ea31c533832072b99c1eb1369871f6d1b357635aa59622d9d52",
- "adminx/language/en/runtime.xml": "3d96eaeb04460caf6230d087cd23eb29878a5b9d047556fdac2a33674105728c",
+ "adminx/language/en/runtime.xml": "68b00572d69484240e1716007ce64c2febe66cae4e021b09c428e758de990ae2",
"adminx/language/en/section-help.xml": "f82ba8d3b78d06d26b92afb81e036b241c8dd3ef9e4ded6158a0be10491e90b9",
- "adminx/language/ru/client.xml": "841a585b544fbdad086db647fb8d0a64678a5947b84f10bd9fc8a10efdadaf78",
+ "adminx/language/ru/client.xml": "b69308cbe26e7da2c10084ce7163e3aacb24cf35447a70a3474963ab0a29ff77",
"adminx/language/ru/common.xml": "d27d863bf82591dbe857317d97a888a23980c87529e2b4c1d4125a6c0ba20966",
"adminx/language/ru/interface.xml": "03fa6351745d2e47c2b3407da65c8b7b048b8e862409dc9bb934e939b9439c75",
- "adminx/language/ru/runtime.xml": "35c0d0a201b45a83169b54fdd4633b4b7f254e68f25e8b2872b3d5aaf417a363",
+ "adminx/language/ru/runtime.xml": "3c411a18695af7a63dd0685a8351ac3a51695aea7d1fa2550fb24cc9e3e82be6",
"adminx/language/ru/section-help.xml": "e821c8589580e4fb898f6f7352691f81348303edab99c338c942511c9bfdca65",
"adminx/modules/Auth/Controller.php": "bad2d7fe5f7eaf4b7f22661945d95df5a3201498b2920e17de5ede6f9f560686",
"adminx/modules/Auth/language/en/interface.xml": "175f757bdc70cb47353a379a6a2303d4903ae7d446fd9a1362919a35517d11a2",
@@ -150,13 +156,13 @@
"adminx/modules/Auth/language/ru/interface.xml": "f8d95a4a8039dabdca7d5f3095feb4904714225b1a285527fd43b0cab65bcf79",
"adminx/modules/Auth/language/ru/runtime.xml": "94f025e2ddd2327ced6aa4740dba80025ae1d71b08f2ce6bd7de9a222277e058",
"adminx/modules/Auth/module.php": "da1ef0924fdaee46a2c1333c43ed05d3afcb02ca16f1994b0eea840a657a4f12",
- "adminx/modules/Auth/view/login.twig": "9848bfbd9da64798c3ba4508dbf7bd413541c3e6870ea95fe3bdf07d12133b95",
+ "adminx/modules/Auth/view/login.twig": "2046a79080fe3f609b88421be5cb62c291402b9717e23b14d8cf93b3c6aa0c18",
"adminx/modules/Blocks/Controller.php": "5665327885171e07f87823b473f97e22f416ab896471a25ff7a2dff498a30d22",
- "adminx/modules/Blocks/Model.php": "1a6d971f4a777e3f91385271ae8e207bacb10cdc1e236921f43a9578f6f9bcb5",
- "adminx/modules/Blocks/Revisions.php": "b67cfbad6830a30dd7f99f5dc4108b33f2bc5d40fe979cab6fb03c3c8f749577",
+ "adminx/modules/Blocks/Model.php": "69dcdd7488f308349741bff0911066c5f1ceace23216ea408fbc79da4a73bc14",
+ "adminx/modules/Blocks/Revisions.php": "b0ff697364a5573c221d8a85545970c8f554af2c352bb51ff7e98930d4e7f91a",
"adminx/modules/Blocks/Syntax.php": "9b2eb8d40171633cdbef6cf21545958f801f4983d62176e9256bec8e4763a624",
- "adminx/modules/Blocks/assets/blocks.css": "a5451ed2acea3a5fd42771188dfa8c85b6911645699e0c219fda519b6175e2e9",
- "adminx/modules/Blocks/assets/blocks.js": "efcda86cd75c8bc2bce2b9ebd32e1a1c51da15119f65f7aa63e45c8d7dd73970",
+ "adminx/modules/Blocks/assets/blocks.css": "13ec7cd2b5a9c2abda0ff2d1419fed50dc1e122edf8ec278e11cb12d75b8ff38",
+ "adminx/modules/Blocks/assets/blocks.js": "8d6633fa870026c2eb014878bcb985d5807fca8040876364be7b3ca34d8fccb8",
"adminx/modules/Blocks/language/en/interface.xml": "6b358b561f81febe1a3284045ff20132737e624a04b2481b51681e258f748e22",
"adminx/modules/Blocks/language/en/runtime.xml": "cd76ddcfb5751496dc4cc160dc2c6f38ae5699a3a5c0041b5fb214fa434f04e1",
"adminx/modules/Blocks/language/ru/interface.xml": "6048c75a95fb474287cf4903fe1b82f2608741b355134be885eadf511da5b656",
@@ -175,10 +181,10 @@
"adminx/modules/Blocks/migrations/012_merge_visual_blocks.php": "5c9cd6efd66a027134621859da1059b2c403bacb732885d6b405699cbce8fc14",
"adminx/modules/Blocks/module.php": "e196ec71bc17da48f9e1663f74db4a7de8fcd28d0f32a3f36beebd8247656322",
"adminx/modules/Blocks/view/index.twig": "39f9225b6a95c65549d0c861afb7db7879ca338c251290653dded6ab7cb715cc",
- "adminx/modules/Catalog/Controller.php": "e9f7add9ea4724221259589d1a861d0233895a150c5a14942325a74a46ac6018",
+ "adminx/modules/Catalog/Controller.php": "996c5454307133d98a1334584d917b2157f62c2fdc0812683fab93b7ab7f5988",
"adminx/modules/Catalog/Model.php": "e7c7cffd91754e04dac322c31a64b420739c671079a7f8e7effb550f5019ee13",
- "adminx/modules/Catalog/assets/catalog-drawer.css": "07ac184a0fbc18954117d5e4d559574400cf6cb7d9b511d852120c00cf17a059",
- "adminx/modules/Catalog/assets/catalog.css": "5587eed0a0c7d22fb8e841b238df2f9477857b0c75d90f232c5816897ee6bfce",
+ "adminx/modules/Catalog/assets/catalog-drawer.css": "c16f11497fafb379db09823b25234f06735341f5f4c8f54026b96de689d894dc",
+ "adminx/modules/Catalog/assets/catalog.css": "49e7a33fc444f8330806e1cc5cbb0d523b673c64faafd25dbf0fe33067643efd",
"adminx/modules/Catalog/assets/catalog.js": "24c1be107fdc88889a0b537b3cc93b32dcf0d83bfe97fefdf9bf10012a132b31",
"adminx/modules/Catalog/language/en/client.xml": "9b156a155a1e6f1a2024f4be5ab8d1051b1ec2adf4b9a0ede71afe11e6d4558f",
"adminx/modules/Catalog/language/en/interface.xml": "1007d3fb9158846dd36f519d13909502e16d3c0a9ca2c49dc24bec088c487ea7",
@@ -188,7 +194,7 @@
"adminx/modules/Catalog/language/ru/runtime.xml": "fe59945f4ceb2bf2d85699623369e7e5889613b8cecd8551a390c78023531d14",
"adminx/modules/Catalog/migrations/005_add_catalog_purpose.sql": "006ff237e25042a4fa9b9f74d5079741eeee1aa52db0cd996ea4c8b817e6700f",
"adminx/modules/Catalog/module.php": "6d8b0d0bc67d314c3aee6f5813c36d32b6f4bc0de884515e13b059927c3aa9ce",
- "adminx/modules/Catalog/view/edit.twig": "6ebdc547619025da9fb9b177875c715b8d9bb5ee6ec3e059e309c1ef7483f931",
+ "adminx/modules/Catalog/view/edit.twig": "eb428848b39c2672c5c25dcd04cece8325230c4b6ed15a3064bd332f31262899",
"adminx/modules/Catalog/view/index.twig": "754c7c4dc81176e8c99b3d9e8c1fda2f62f03df341c3df1f7393f72d43c6f8e2",
"adminx/modules/Console/Controller.php": "af0309a50d7f4366279be8ea12959cf7718a3c68b895a65ec5846554fd6a4857",
"adminx/modules/Console/Model.php": "53aad480b8d4e5d68c4e9345d219ea5cbff6d04eee7ce4aa15deab6f00dac1cc",
@@ -206,7 +212,7 @@
"adminx/modules/Console/view/index.twig": "c64fe9632db751357ed98cf2ddca524001e71200297e1b50f0d5c827483933af",
"adminx/modules/Customers/Controller.php": "1aebb30a2d85343ace1719fca451a105cfe9e79cd3fcde6dbf8b6d907c2dee23",
"adminx/modules/Customers/Model.php": "df097775eacfe0aac5c3b7ad4fdab7ff0816f57b77f94ed7993acd221725a3e6",
- "adminx/modules/Customers/assets/customers.css": "adabc3a85737d66cb4042a4262f4ed703643b469c379468c5b7e626131fd2f9e",
+ "adminx/modules/Customers/assets/customers.css": "fdbd5c97456751080d134f8a97fc85b116098c7a29c4933600ac23fd0e33adbf",
"adminx/modules/Customers/assets/customers.js": "babfd39619f4a45717e98b15dc3ec29976f7206a805880b0735c10a5de601b6a",
"adminx/modules/Customers/language/en/client.xml": "21310e86bb84cc1fbaf65fad3f09bb4d058e820d919a9af6ae5e2700c2e8d1b7",
"adminx/modules/Customers/language/en/interface.xml": "573a7ca126beeaff77a8865d6114dac0429b588d8061f939c5ed7af316d4070b",
@@ -224,7 +230,7 @@
"adminx/modules/Customers/view/index.twig": "5f19c30e03367b0bcbc83256d727eb19ee045ab3475521531faf486a4d446fc4",
"adminx/modules/Dashboard/Controller.php": "7e6ad0610df7581bb010cee574d8d093c4737af439bd2b0763142f50ddee1c54",
"adminx/modules/Dashboard/Widgets.php": "56fc7ba7c1f9e30d7eae8c61524b6441b1c706a2cd56b1db883bb8bff7186eed",
- "adminx/modules/Dashboard/assets/dashboard.css": "b38927851888ccb5bcaaae3c22243ff7d727b1ea2a585053d935a404908e4900",
+ "adminx/modules/Dashboard/assets/dashboard.css": "8e28b56cac2e55d9b57304ea6e265fd94a52400674c082fbdd78f1eed1872424",
"adminx/modules/Dashboard/assets/dashboard.js": "ac683db96673fb4f70b8cb2d23ab7eda2251f96cc2852223f5ee68bd11a911e7",
"adminx/modules/Dashboard/language/en/common.xml": "10445e46f7445366125f761c2d29567637d6d2c9ba1e9766136280ff28c3d465",
"adminx/modules/Dashboard/language/en/interface.xml": "58a20edaaf485d5a3131d7684974c2d18bedea408a1e28f5f47fcd3fae025159",
@@ -237,12 +243,12 @@
"adminx/modules/Dashboard/view/widgets/recent-documents.twig": "d03f36f047574f8ce57fdcfd79b26d9b20ca01dba69ce7c71a3e9a277f0105d0",
"adminx/modules/Dashboard/view/widgets/shortcuts.twig": "af165425ea7889cdc3edbd72c7f0dd9dc5c4c06486bc4ab37816112cfb696718",
"adminx/modules/Dashboard/view/widgets/system-kpis.twig": "52dab540a189443b76811ac4b457a566cf90134366749aa9c7d82e2d23ffdaa3",
- "adminx/modules/Database/Backup.php": "17e115967f1468803c07a1514df84ae9111b8f2b0e0a936efb7ce0312dc88458",
- "adminx/modules/Database/BackupRestore.php": "370eef15faf49ee0e8e1f24fdac89a38ae2833a24d9c02c6778786de03ee8a41",
+ "adminx/modules/Database/Backup.php": "cab7b7d1a8ab9d2d18ece85604670458a851665b0af64f7c37d5763cc658c464",
+ "adminx/modules/Database/BackupRestore.php": "1fb34abdec27ed24665ec0749f4ed44167f3881d3477129d5d4de6c7b36d6653",
"adminx/modules/Database/Controller.php": "c3f59beb5874b8959a5a37c49fecc9b6b48581bf20284b73290c44fb91a14363",
"adminx/modules/Database/Model.php": "52c23131d07bc798fb2a3489f91ae909985e60d2965e3cd5fbaf5ab441d85477",
"adminx/modules/Database/SqlDumpReader.php": "f0b3a501d0afea0ae50d35b43e901844e14d9faec286738956dc6f2b1c5ea4b3",
- "adminx/modules/Database/assets/database.css": "fafcc546e3c5d5b72656a17e4a150ec9eff37e91b5ac0c8a1f0058d475ec8295",
+ "adminx/modules/Database/assets/database.css": "aefc5ac895e05190df663d2a7a384430bcbdf2cef92ef1ccd3b819524a20cdbd",
"adminx/modules/Database/assets/database.js": "f54e2756a54eedf5654eee7357a022478988293c9419bb7ad024eb8f69ad8c0a",
"adminx/modules/Database/language/en/client.xml": "4464d65adfd150e597436cade1d36b0c0436654fb4c710218fe2fad117b12369",
"adminx/modules/Database/language/en/interface.xml": "da319d6e408fd13179bb4094bff6cab3c72880f691f2e80a3ff13245144014d7",
@@ -254,21 +260,30 @@
"adminx/modules/Database/migrations/002_module_lifecycle_recovery.php": "779b04c8b35f3d81657848016536a9e3ef70fb76dd3ce5b1ba4b4a72166f7a2d",
"adminx/modules/Database/migrations/003_document_fields_text_composite_index.php": "541a8df87dd5fec6946f26dcca88f78a2bf5e09608f9300baa8532202020ffbf",
"adminx/modules/Database/module.php": "61c12758ca1a7b59907c13cd9dbcc39ce771fc671edba665d7b6d4bcf82c9aca",
- "adminx/modules/Database/view/index.twig": "e1b18f0ea24c95683477c7ea28bd6094e8589e6dad2acb627434ff7241d7a410",
- "adminx/modules/Documents/Controller.php": "ae8fe6ec76b2521fcbd336913a3de9750dbe07f80b568adc45c4f228f66d0259",
+ "adminx/modules/Database/view/index.twig": "566ef7db0a78b98ab1e41750dc364c073735404c62576f7995ddaa4a6d51828a",
+ "adminx/modules/Directories/Controller.php": "bae664ccff5a63a215fa1b59b721d0b0a4055ac3633319476d09889ead0d7d27",
+ "adminx/modules/Directories/Model.php": "ead423e3d0a07badf5a773780ee37d92394240fc753c10fad274a3131db7c2ce",
+ "adminx/modules/Directories/assets/directories.css": "6250526e076f3da647c1e696ce965ae2d9d3ddce890b8cf022af4b33aee18e1d",
+ "adminx/modules/Directories/assets/directories.js": "8e0b59128ab92a07731009190716ad3dc14f99ff7ed2bef299e1df645a4b82a0",
+ "adminx/modules/Directories/migrations/001_create_directories.sql": "d1f84fccf75892fc1dcfe6b770112101e233d96d9f4866a08d1977646a690530",
+ "adminx/modules/Directories/module.php": "562709b6a6ca8bea3e2e05d1dde05af0ce7e627e94d52df3b161349dadeb90da",
+ "adminx/modules/Directories/view/index.twig": "0bde23bc578b9ffad3c47534c304c0016b661a36588d6d3c9ffb92e2daa388a0",
+ "adminx/modules/Documents/Controller.php": "8cd40c0e942f7eaef30af8afea990746014df03c4063a69fd6e34019045fd72d",
"adminx/modules/Documents/DocumentHookRunner.php": "7e0cd24ed5791b6d5010e4a05351b709be09b6df1e700a3ff06685b9078b2b1d",
"adminx/modules/Documents/EditConflict.php": "ebc3ebb92c3fc3ccf3909d2ff517014fc888ee20576a520b4979f68bde632b97",
- "adminx/modules/Documents/Model.php": "ec5b52309ee149123ed333f4373c5c7e94045701314e887a1ae870a4d7667058",
+ "adminx/modules/Documents/Model.php": "60ad2c0508a6f1ad1e090dc54cadd19ef864fa9657558c5d82291dc30796c15e",
"adminx/modules/Documents/Revisions.php": "5d40ba1ae4c9563e14464f795b054f9d4edb05e02c8a719bd796126054616132",
- "adminx/modules/Documents/assets/documents.css": "f09b629990eb6beabb6909ed239ac761a2da2a6e902d0b588506f3ce76a0a477",
- "adminx/modules/Documents/assets/documents.js": "acfb3b096decdb6b725118c9013f1098878f13737df0f23c0df2744289260577",
- "adminx/modules/Documents/assets/redirects.js": "7297896b340be6cabecfeb220dabca1d63c8040e2ceaf0d3a816f955c3ef45eb",
- "adminx/modules/Documents/language/en/client.xml": "75c2550fd7ab123d14efda13c575d474b3989216c9adbeb70d08b3f40e6523e5",
+ "adminx/modules/Documents/assets/documents.css": "c7898e92c20f71c155928eb4acb298a3975370269f449960c7576776c9134b9c",
+ "adminx/modules/Documents/assets/documents.js": "d1af08bd3869764c103cd97880d8d82cba1d233fc4b5dfb01885064713ed6750",
+ "adminx/modules/Documents/assets/redirects.js": "95ca395c6b4fd3814da37450c27da351e75f509e26421c975dd8e0ee5f657efd",
+ "adminx/modules/Documents/language/en/client.xml": "10de693182945b4c5347aafe508c212ba193acbc1384812aa2c802b5298bf908",
"adminx/modules/Documents/language/en/interface.xml": "f3a30b3c565d2e01c841c843dc529fed9844dc2e095f90b7902008ded7214ed2",
"adminx/modules/Documents/language/en/runtime.xml": "7009fa27d67ab168fecd9d9bf182216dfb0397d9c006eb6563ee3c464daa5b5e",
- "adminx/modules/Documents/language/ru/client.xml": "b5c50b64528eb4a7cc324222e6e51d88889d892be2581baa6c68fb877916f9f9",
+ "adminx/modules/Documents/language/en/zz-interface.xml": "839ea37d585ff10d7b819a4fcd4bda876c94d51019489cc8d35af15fbc905f2b",
+ "adminx/modules/Documents/language/ru/client.xml": "14f67aa847553269adb37efa6bb408d866e30f8db0eee92810564e0f7eb67305",
"adminx/modules/Documents/language/ru/interface.xml": "718386366f913c590c2bb881f49ad3b7d436de82faeec30655a7c2f9b14d58e0",
"adminx/modules/Documents/language/ru/runtime.xml": "6c3e9a2dbf862bef5a76ff0bb600084fcd9d5d1a7f0cbfd02377242b74fcd759",
+ "adminx/modules/Documents/language/ru/zz-interface.xml": "2d819845ff2ae5246203374a685f8a5ad881ba9067166161eb96d5c9271026dd",
"adminx/modules/Documents/migrations/001_correct_breadcrumb_title.sql": "e2b612f19cbc2c419d1a3d2c684909b4055811a7121f4debdf6b83e99bd214cf",
"adminx/modules/Documents/migrations/002_correct_legacy_breadcrumb_title.sql": "1c1a2880dcc9ea69ca907767a6333a562d6f0f30da159e35c55377c0594fb8a2",
"adminx/modules/Documents/migrations/003_create_document_api_tokens.sql": "15432eac6ce1b6f867b5a2ccdeac28900ab2382d0dc775ebe13e305e93603b87",
@@ -286,46 +301,51 @@
"adminx/modules/Documents/migrations/015_document_creation_presets.sql": "a123f8867ab6f6346002d08317acac37062c2792913ad14102c81812d45571bd",
"adminx/modules/Documents/migrations/016_reconcile_document_content_columns.php": "06671bb26bf29479dce24888fb3712c0d20e2aac5b3d70196604ce4097c875fc",
"adminx/modules/Documents/module.php": "9fd49d13abaa566c76d4d2d7b0e4e2dac2b7d58d1f27e3545400b3a1c900ebfe",
+ "adminx/modules/Documents/view/_product_panels.twig": "e07898ea6a7cc0004ac7545b689afdc6b474e6d8b24357f5c5190b4ca5589142",
"adminx/modules/Documents/view/api.twig": "60e004a449d5df2b39e24a073acdf3622b2d379e668bca5834d59bf9e3e07294",
- "adminx/modules/Documents/view/edit.twig": "c3836a0fa03a5b8d9434e110f21ba24bf8fb9bbbdd3acb20e6fbbdf472cb4c80",
+ "adminx/modules/Documents/view/edit.twig": "206ad52382062e3603601f038638f26c5602a5a01b0494eb80c0902d8f266f6c",
"adminx/modules/Documents/view/index.twig": "b6f005bbb66550e43f59d75b8311f290e6c728687473a1a7da2dd74af56b8a7e",
"adminx/modules/Documents/view/redirects.twig": "0d8e6c0431c3cb4571a40f021ad3f55a9c2df1092b500fa3735258bb80231d7a",
"adminx/modules/Documents/view/views.twig": "07ae0d509df51aeb551ae5aea2a19766c1cb00fd255ab3d9734ffdb7e55764c8",
- "adminx/modules/Events/Controller.php": "6713fbd79269d2bdd159a15b8f77ac1de7cbd40ac4e487778bc61a4901e85026",
- "adminx/modules/Events/Model.php": "bf62126857bc4e43c1c6c594b0f51bedeee76a58bb92b990be43f925c2eca4aa",
- "adminx/modules/Events/assets/events.css": "8a21ffdee25078635c59c79290dfe14b941fd63859fffd2d17d13fd5b488fac1",
- "adminx/modules/Events/assets/events.js": "b07c0e4433ca6c0ac1a5b8bbabd7e025f23833a2443c03cddc0b76d6be2765bf",
- "adminx/modules/Events/language/en/client.xml": "e198b73ef8a84a7606f98ca6caa8039edfac2bcf3b4652c56b8dd9460dbd193e",
+ "adminx/modules/Events/Controller.php": "c57fc25e815c47c14626371d044079e6711f0a4bce2004d2b9256db62f0d0e85",
+ "adminx/modules/Events/Model.php": "a3a09370aa47766c7c4e70ec002af98a13bfcad3151b9ecb96a6ffb6cddf7ae2",
+ "adminx/modules/Events/assets/events.css": "91457d384056a6ea96bdc6bf012c033c19e0cb21401460ae9b0f9398493258fc",
+ "adminx/modules/Events/assets/events.js": "d4c6cc636473ecd81bd1551c37df5b96434e7e35fbb251f93b3647a25f52d336",
+ "adminx/modules/Events/language/en/client.xml": "ea0be743665d923eed666b97590a63ad078caa2d439f18ccf78c22f040ab9e24",
"adminx/modules/Events/language/en/interface.xml": "8ba72b194174cff23b1678a34930d0fcc8fbb51465a6e0457cd503c2d0119dc1",
"adminx/modules/Events/language/en/runtime.xml": "e6d14b92215da7d85bc8460f2e42410c0005f51c1245533b9d57c62fc211a457",
- "adminx/modules/Events/language/ru/client.xml": "ca18c62786e33a251fd3c903fefb64d0fed84a5f8a62d23299ab81b8f51284c1",
+ "adminx/modules/Events/language/ru/client.xml": "e7799f20371afb59047dcc0b640aa43a5c047761a7627f98b56b4e8d93dad612",
"adminx/modules/Events/language/ru/interface.xml": "701b5862e628fd5e4d64ac4df5983d1f40b8aa1b2fc67b4eccde0a1143338e27",
"adminx/modules/Events/language/ru/runtime.xml": "339a6e6c117e3e14e871acd38ab398ec4f54f0bee3785a9c3f5d90048899a9ec",
"adminx/modules/Events/migrations/001_materialize_event_logs.sql": "59500ca428021c9dce543ff9a7711e2b729f7e9566ec6277be09939e78b9ddd0",
"adminx/modules/Events/module.php": "d506c314dfc71abe7216ec80243f46549a833a6cc3371333d1e7a6435312c46b",
- "adminx/modules/Events/view/index.twig": "eade5c87c3cb76dd8ea4795ad72927848a40fb0f042e4938bf31d785f8c33b4b",
+ "adminx/modules/Events/view/index.twig": "7057cee5abe6ace177509c54c2d1736ab88b96f9f17f6db2bec0553b4eb1ac5f",
+ "adminx/modules/GlobalSearch/Controller.php": "6a109bbe2ca833df90138af056cb6cc32c313677d3bda071b3638448ca7e33fa",
+ "adminx/modules/GlobalSearch/Model.php": "5cce4bc4358c2b7b0f54e76f77140470dc7d6413176f37e97808553f11aeefcb",
+ "adminx/modules/GlobalSearch/module.php": "b7b5d9c9179500edbbb82a7ca0583374ac2a13bc8aa3f2eca5c6b9242d6ddbeb",
"adminx/modules/Groups/Controller.php": "37959bbc4cf73ea2968616c195763fbd1bbe14cea90c01d4dae5fad2970803d6",
"adminx/modules/Groups/Model.php": "889ba3e4c93ee59ddd6515f4fa4b5ce4c82335e91b3dc6eb315dd095ce6f8fa2",
"adminx/modules/Groups/Setup.php": "b7140a87474e343455fa81809f8f8f2dfe7e1ee3640c5f024a1bb06ebd783865",
"adminx/modules/Groups/assets/groups.css": "6d16a57db9ab5f42f8722b52dcecefb02a51d68360b9855187dfece83b36191d",
"adminx/modules/Groups/assets/groups.js": "1e8e6c9b183cae19587e823f037e2dec5e69aedf10629c555edeffe68369a64b",
"adminx/modules/Groups/language/en/client.xml": "dbceb0c80a844d05f3c370dbc495a71142818c0c9e3ff94c33586bea89f07831",
- "adminx/modules/Groups/language/en/interface.xml": "54469c9116673d33e55fb26426dae68ab864a64d3dd2afb99312ac6ac14f8bc7",
+ "adminx/modules/Groups/language/en/interface.xml": "ca7dfd681c7f1e8580914cafdfd259d5d3be84868e7ee97288fa36ab3f785de7",
"adminx/modules/Groups/language/en/runtime.xml": "7ab07b963b2b29f046f6c363f04c1104de6d5914f5e70b6b8cd42f5770847168",
"adminx/modules/Groups/language/ru/client.xml": "320b9b55d6eaf6f4d3643f3fa4675544a779540ec34b3b94e03deac6f57579b0",
- "adminx/modules/Groups/language/ru/interface.xml": "d6175d32b3e971aa03f4f6650967cc261ca0bfc04928d5c7cbbedf536b6c1a8c",
+ "adminx/modules/Groups/language/ru/interface.xml": "6f9da63ae65ecc8890ea16dbe7e879bdf5520d5f74c0a6204285b19d580f15e3",
"adminx/modules/Groups/language/ru/runtime.xml": "eea7d7d2b88befb79b50d00c89dc63d0bdfa95883483d9606ce0605c03c92402",
"adminx/modules/Groups/migrations/001_register_public_debug_permission.sql": "a81a4843b74ad72aca16c8016b767dca248b2ea950673d99c6945d1b8b867fe9",
"adminx/modules/Groups/migrations/002_remove_observer_role.sql": "310c6d1a9bfa5693f4a3f680f63279ea9dd040e51284d6cf5de9ad519bd2152d",
"adminx/modules/Groups/migrations/003_seed_default_roles.sql": "75cda12582ae86e538fdd455d064eacfcadf5f4dc50e51838718f154d73ed750",
"adminx/modules/Groups/migrations/004_merge_legacy_roles.sql": "3ca98bc7f0348d04594e42beb8768a6fde6a732d39e7528068efc0b89d9c5ce0",
"adminx/modules/Groups/migrations/005_register_development_site_permission.sql": "0a87db84297b7a8b5959217b82b6dbdc88507a822b342e145ddb7d800f50a73e",
- "adminx/modules/Groups/module.php": "9f3bd0191fba171c4bad40ada6274e91b1a9f8c5e4d994bd27c02a2a894b7965",
- "adminx/modules/Groups/view/list.twig": "7cb21300defc1873604c0af45b98698f91708057cc0853caef6f66bf0277caa5",
- "adminx/modules/Media/Controller.php": "0ecaaa26824e737b30aaa0b68f63e7405056a58e399ded779568109416ee7eca",
- "adminx/modules/Media/Model.php": "12f4fa4eec705bcecbbaedfe07a7e25ef57062fa72e937333ade840557be1cbc",
- "adminx/modules/Media/assets/media.css": "f3e04f005cd3930de211245f912ff35044ee3f5f2b5d86d7384b1dbd026d592f",
- "adminx/modules/Media/assets/media.js": "0182e6b649a3993a5ec15f73a63ab89c330c5f3d1d901f848a16e66952615c9a",
+ "adminx/modules/Groups/migrations/006_normalize_control_panel_permission.sql": "eb58f4d6a59efe656359d003d81f995e989393d7235a3fae7e38c0733fe174fc",
+ "adminx/modules/Groups/module.php": "4f1d59f9aa351b18b111396d74001597ffefa744cd55240c7339fee06f009542",
+ "adminx/modules/Groups/view/list.twig": "48949134b29f8c2f62bef3d90e1438131e5dbc53cba84214e11e5cbed10040ed",
+ "adminx/modules/Media/Controller.php": "961186cbc05fdefae407d7af92075bc7871415769cd40eba7aebc5b6f52fb762",
+ "adminx/modules/Media/Model.php": "c23ffb325ba7d3f86b1088778c24ebc6860047fc19ba038fc061a10d121d2c0e",
+ "adminx/modules/Media/assets/media.css": "277e91e70607ef85c2a71a0a2d1598cfc3c87a70e0a3f45b73548397fef74278",
+ "adminx/modules/Media/assets/media.js": "1fac7fee094cef4f4726f92fd966b8fc02bfe7cbae960e75d9fc7cef901d2d64",
"adminx/modules/Media/language/en/client.xml": "06cecdb6cacdd0871334a3da0a3c7be27d7f835e2a048d37ea31ae4f37469270",
"adminx/modules/Media/language/en/interface.xml": "2eab0a0d6acf094c43c690e4c09683378a26dfaec7e258c23f3f7bfb1fc9e9a7",
"adminx/modules/Media/language/en/runtime.xml": "8768433d736f0e8b3508b1a68de1281b2f841c2a9decbd8195603a31bff3a92f",
@@ -334,25 +354,25 @@
"adminx/modules/Media/language/ru/runtime.xml": "538db360abf2d61e32d6476846173ea4f5115703021362314a9100df3854d6f7",
"adminx/modules/Media/module.php": "6e5b38b7da0d2fd506900af70a8813162e3e6215fe403e969ab914a9c20ced1e",
"adminx/modules/Media/view/file.twig": "c338f333ac0424c224e5d73d1e3ac1854000155f3a06d6137992660bb2f407b2",
- "adminx/modules/Media/view/index.twig": "4dab58fac2cbb79d83c3e70bbcf6545dd375d8b2397b2a3fd1e85ec8d0b83ec2",
- "adminx/modules/Modules/Controller.php": "a3b42def293e69921dc72c575146d6c5828127c66c53ce62d30a0cecebba5051",
+ "adminx/modules/Media/view/index.twig": "d718bcac68d2de3046c4d6a551dab38577d5a9a8ee6a9f8733a2a8c640531bf9",
+ "adminx/modules/Modules/Controller.php": "de3c800b32a05ab41dac31d4964242fb0635aef6ef32f69a332509159b8e755d",
"adminx/modules/Modules/ModuleArchiveInstaller.php": "22f015a6695de9f4fea12a580adfb76837e59ee4e9de92c153782c5a40a97560",
"adminx/modules/Modules/ModuleRepository.php": "651efb2eb73beefd4d486761d0a554d45725f58aab47bcb6e88d7d61a0b8b87c",
- "adminx/modules/Modules/NativeModuleAdapter.php": "aa27011ea06d71b440476d23496c06d5a55f776a3c35c1c9e37eb44239616b8b",
- "adminx/modules/Modules/assets/modules.css": "c5f0ce469db594c6706dd4e9766413fa49428572306f61603cf787afd9c29881",
- "adminx/modules/Modules/assets/modules.js": "dcb0622e6a571f25d9478a937ea0d298ca2185624b87469b1ab3afe5483b0a0e",
- "adminx/modules/Modules/language/en/client.xml": "0fbfd035fbea33f38e12163a97e463bb27e4f7feb27a93d41e1a703e956512d7",
+ "adminx/modules/Modules/NativeModuleAdapter.php": "607ed915ed6a956250b8625117084d9b5c46f149235e99457a514016d7ba375d",
+ "adminx/modules/Modules/assets/modules.css": "ba3f1f6721dd1e529903254abe8fd24e20873b6c00e0a2d2d2d7bcc65170d884",
+ "adminx/modules/Modules/assets/modules.js": "7ed1e03f0114ff942b4db057ca57586c2cc94ed55a2d10f5ca5dafa908acabb5",
+ "adminx/modules/Modules/language/en/client.xml": "f54084951cb19973f33c9a28576353a09050129f8305d47590465083c0130791",
"adminx/modules/Modules/language/en/interface.xml": "21596992d04ddbdafc36aeb26d3451f042571166426a6a0007f698db9baf0007",
"adminx/modules/Modules/language/en/runtime.xml": "df647071e7691a8dc3634568ec775e09886e57845834772177083724d7d93e56",
- "adminx/modules/Modules/language/ru/client.xml": "01b635097b86fcf9fdb76979e5275a2497880d9e029a2bf2192530e0628b6bcc",
+ "adminx/modules/Modules/language/ru/client.xml": "176d4ebdae73e7533082676c2e56199d47a43336ace47fbeb31392adad26d212",
"adminx/modules/Modules/language/ru/interface.xml": "ee33c8914bb44478a8801ae23522954c860a6ecb007b848bb956fc07d5be4ce7",
"adminx/modules/Modules/language/ru/runtime.xml": "271e31a8ae1586ed045861933a6db150427c78be7560942185633f35cf90686a",
- "adminx/modules/Modules/module.php": "173ff87577a9016d30cb9fa928d05eb4f532264d8f27c7f84aa443ffb6e20492",
- "adminx/modules/Modules/view/index.twig": "2e9e19bd26082dbab628f979e8469a7a1cc6281901c2230755f44ad5a5d51ecd",
+ "adminx/modules/Modules/module.php": "d3954b60d4eef716ba12e4782999fde169210d1cbfd35f0e6b9887c22b3f77e3",
+ "adminx/modules/Modules/view/index.twig": "2ca402c0886f611129e884129f6e0b0022e14a8c385d5d4ad55abe49ee39f5c2",
"adminx/modules/Navigation/Controller.php": "796d25525322eb63e0428ad5f9964ae52ff3d91fc9aeee275c9f854aeb99a5de",
- "adminx/modules/Navigation/Model.php": "484e16c2c36d4880e7428bca45f8d50e4a838d40d58c98230a635c830582ca3b",
- "adminx/modules/Navigation/assets/navigation.css": "030f3dc05b22ba5559540ae6dbb64b2842350645f14e834fdc15ff269d7ece78",
- "adminx/modules/Navigation/assets/navigation.js": "2dec089bf9f9d4eeac8119e07f8ca7e0c0914f2175eec1549e48103c9577d30e",
+ "adminx/modules/Navigation/Model.php": "46909ee83e0aff609337ccd942c7a69765d734ffbb47bea66015f4a821f0c4b8",
+ "adminx/modules/Navigation/assets/navigation.css": "66d2ee424dcb1d2044c337498b570e1c2539c7148ad19975fc776e8f5ed94ffb",
+ "adminx/modules/Navigation/assets/navigation.js": "b9c23fdd643e00f6f28cb57f4d75f4687bbd6d7b32b76f04df4126e24440f2f7",
"adminx/modules/Navigation/language/en/client.xml": "9a0ad0248941cd6377ada049666387581bea0dd76353d34779b4c1343b74ba50",
"adminx/modules/Navigation/language/en/interface.xml": "5ec5a64240356c4b87f65baea911762e64e88dd652c1fd7c801079247d697b79",
"adminx/modules/Navigation/language/en/runtime.xml": "341b984ede453a135b6e155fd29c08d9d5a2ab16e62046379f982d9a56dc4a81",
@@ -360,28 +380,31 @@
"adminx/modules/Navigation/language/ru/interface.xml": "cd5ac333b85534be6a81377a1b40fbd449174cf2d1f019d25b3bb3f5ba0713a5",
"adminx/modules/Navigation/language/ru/runtime.xml": "45d994c39fa7fa092525ed4a61d6775008f3993f4cdcdd615d61e5f15a749cff",
"adminx/modules/Navigation/migrations/001_create_navigation_tables.sql": "43c937b8d7d7f5b70569c591db58ee8592386b988e8cf148fae723b1f68b3012",
- "adminx/modules/Navigation/module.php": "1be9c185ff36f11d4d03aa93a75ca0b889f6884a006ec801ad9005527b69e235",
+ "adminx/modules/Navigation/module.php": "e7783bfdceac8787da92d10be2ffc38ed4a8988db4112e83faec8448c8ee4656",
"adminx/modules/Navigation/view/index.twig": "870b617bc857aeb3838e76ce79f0a7fbb81ff299eaa0155767fef5b86fbdf65d",
- "adminx/modules/Registrations/Controller.php": "90ef3b986abd7b5062cd02009a8cb8b174081fd217166c019666fcb94904d8f6",
- "adminx/modules/Registrations/Model.php": "689ba41add40cd61edba2659742931c4a9256ec4caa98066996b17f34a50091a",
- "adminx/modules/Registrations/Schema.php": "d61127fd2e8b5a9c53ac3c4a9f0348edacb9552674a3fd5708bdf246e6bb072d",
- "adminx/modules/Registrations/assets/registrations.css": "0d7a49bd09c22a968c3b2d51148c54510c564cf63578099931c441197a54406f",
- "adminx/modules/Registrations/assets/registrations.js": "fdb1647b7f9e1ca9ddf3bbac81dd6a3405af5616b878f24f51ca17c216f938de",
- "adminx/modules/Registrations/language/en/client.xml": "a58b1540a3505be6b05ac2260914f9796c894b059086694ea722cd37cda3d056",
- "adminx/modules/Registrations/language/en/interface.xml": "7ab5d6b95157c613e5d010570ea4fbd95c96d257267764060dca415fa23b7d77",
- "adminx/modules/Registrations/language/en/runtime.xml": "69986681ad2573d277a63a9b87395aef2d82a3043cf7f3c9db5d146ce1be67dd",
- "adminx/modules/Registrations/language/ru/client.xml": "665a6fc93d2dd3ac3c5453cab99fc655e3208ad06262c26c90758edd67481751",
- "adminx/modules/Registrations/language/ru/interface.xml": "072128419f1ce3ddd7f4faa3d1605ec34e0368ef137ee2ab87e4b5d2dee5682e",
- "adminx/modules/Registrations/language/ru/runtime.xml": "cd3fc8a2f1a1e0afb2476d29947ec29dd8d1586254ca7f32420c73316b8226ae",
- "adminx/modules/Registrations/migrations/001_create_registration_certificates.sql": "e843c57ec87589057a7f63d2b2957afc68417d4dcbad255cf77d0c073916e976",
- "adminx/modules/Registrations/migrations/002_register_registration_permissions.sql": "b905299b8da097a3706e6f93cc1376287383e77ae9825a255d566cb01da23e6f",
- "adminx/modules/Registrations/migrations/uninstall.sql": "41ef0a6f74661e513314140e4f65e1b859a8c47c050afffa6696928e8e31788e",
- "adminx/modules/Registrations/module.php": "b98ab875ae628efd3f729f84a1c9f24bd2d4f96e05664b5f455a8c60934ec7ef",
- "adminx/modules/Registrations/view/edit.twig": "2e7e7859acd0da4be37990d96dd2fcc9224cae62e89d47f55803ad99d070c82d",
- "adminx/modules/Registrations/view/index.twig": "16e488d00227ee1c78e1df68fdf671f75e96d188f18832482c069e4402851884",
+ "adminx/modules/PublicSite/Controller.php": "91233dfdcb4d893511f0c7f13db8673bfeace19fce2d303a82d7c4b849f4c00e",
+ "adminx/modules/PublicSite/Model.php": "48ae7a07d130aa6ac6df195ab12b2e2d8e00e31e09a4e8bfb914f40eda8c93ad",
+ "adminx/modules/PublicSite/PresentationModel.php": "3407e5e29165c264c65527433cb94d7d13a80f724dc4d1bf23a49cd2de88b125",
+ "adminx/modules/PublicSite/PresentationsController.php": "e2c7cc517f1a38d6b5e6828b1d652caa6b63622532d2f1f3c5f176e164ba6533",
+ "adminx/modules/PublicSite/assets/presentations.css": "fddb0b4300129f7d7c33853b7e31bce131eb6f867e577443429dcafac8cc67d4",
+ "adminx/modules/PublicSite/assets/presentations.js": "3a74e7c7ae3269b98eca52d2d61db1f4f21871c6f4a5425cc7bab58d24efd963",
+ "adminx/modules/PublicSite/assets/public-site.css": "e66be43cbe9df41e5fac5317a6a3ff405afcc6713c9777629cb05e76603f298e",
+ "adminx/modules/PublicSite/language/en/client.xml": "5a93fd6babc20670e54795211cf44ed155417aa933e5d4a50be21aebfa7bd510",
+ "adminx/modules/PublicSite/language/en/interface.xml": "e5e868af542e9fe6c981dff18e31a0050d2d7ae2b8b217e7b71bd98b496b5f5b",
+ "adminx/modules/PublicSite/language/ru/client.xml": "f49060544db65a516b2a643420871571b4da5911ae800075c01ae35e55a5d639",
+ "adminx/modules/PublicSite/language/ru/interface.xml": "67a7d1a651d8dd4ca3edc6c281d68c15146a8e84cb18833f9f4041fbbd57a619",
+ "adminx/modules/PublicSite/migrations/001_create_presentations.sql": "023d97f077af57107549dd9e420f5ed55a278fb8318caeb8d86f2dc7750dbfbb",
+ "adminx/modules/PublicSite/module.php": "a69322a39a6b658c24755a068efb42df73d89e41a11f8354cebf07f93aecb9a8",
+ "adminx/modules/PublicSite/view/_placement_pagination.twig": "b10413bdba07695500803625486b56ae5283c5a0a6dff83c668c8f93d1eab182",
+ "adminx/modules/PublicSite/view/_tabs.twig": "dc0a2f83f9f6a917357f4a327fffdc59bfeda83f69e2a0bf0b9805de93237b9d",
+ "adminx/modules/PublicSite/view/diagnostics.twig": "242682325bd7c99c75f9694d0e7ff6217124bf093364c027d13f609db660f4b7",
+ "adminx/modules/PublicSite/view/index.twig": "54ea9422b90235e2926924f11c43b56d90ca8e6c4f255501fcc9c52068cb6f92",
+ "adminx/modules/PublicSite/view/map.twig": "11fba1f54578b4908c669a7dae128949f53a7f1f03746f45a5059c92f473385d",
+ "adminx/modules/PublicSite/view/placements.twig": "c4c2af900bfae3ceb8d35802a277429a69afe47ddad49b6a6fd2509207dff36e",
+ "adminx/modules/PublicSite/view/presentations.twig": "d0b245362001ef2a1508fc5c4fa462895b4bbff104025544656828c4513d4cc1",
"adminx/modules/Requests/Controller.php": "c6d2c56bf10f28cfdda15c0f6d75e134ab5029087954fc44857323462029f1bb",
- "adminx/modules/Requests/Model.php": "5a4c038c97b643fd603d16244bf7cb5654bc1d91b72bfec18a533203ac9ee7f9",
- "adminx/modules/Requests/assets/requests.css": "a0f0a671d1dd2e16eb2a26b36c4e5381437c3996563f00cc03bf447bfadb7355",
+ "adminx/modules/Requests/Model.php": "211e9f5d7b40d1d174c75d92673c6072f10d6ebb1ddeafaa9a109483b042c222",
+ "adminx/modules/Requests/assets/requests.css": "c62d55179c0413c398c3bc2743a3f7e66e0cb653c85a24469ea8940ec5e9a438",
"adminx/modules/Requests/assets/requests.js": "980bc0c1c311a40e0c49facf0a2b0dc984a3b05c8291ffc6a66f7942762b74af",
"adminx/modules/Requests/language/en/client.xml": "544406edaf2b568062e697f7264b5c5fbe3fc45c27e019615601e831612db0bc",
"adminx/modules/Requests/language/en/interface.xml": "766190d3f850f6fc414a4646d786f79f394ee8f03524b3a2ae820fbe792d4602",
@@ -402,12 +425,12 @@
"adminx/modules/Requests/migrations/011_order_tiebreaker.php": "533f3bd0cd40af166feb13dd08ad1a57dbc0a14e044cc10827ffcfc091ff178f",
"adminx/modules/Requests/migrations/012_condition_value_sources.php": "c874ea024343e4d698cda7a138e13b23776a58f51706c2a16102ded1234b2aa2",
"adminx/modules/Requests/migrations/013_sort_rules.php": "4c58c56d9766c16517bc2ceee1d6313b1728e564a332fe9a5cdf69a04d7afe70",
- "adminx/modules/Requests/module.php": "d950a229b063efda170bec7995678eddd27cffe706c500f8b979b938afa044f9",
+ "adminx/modules/Requests/module.php": "c8fae975cbeec874dda310e5653c6887e34a7ce14bd33b03b72a664d68145709",
"adminx/modules/Requests/view/edit.twig": "319e06df37e3d5d449d7a7c6b2078b2049ca230109f9b754de550b3c34aab282",
"adminx/modules/Requests/view/index.twig": "164490c16e041b928e1ecf604915656399690055d1f7de4bc88caf5f5abd485d",
"adminx/modules/Requests/view/native-audit.twig": "42120fd41aa4afa39560fea3f2625a4e78a61cb4f9ccc0c0388ca319d4f39c06",
"adminx/modules/Rubrics/AdminView.php": "a866300dda84253046f88d19a4467ae1a545c8b1dcac7672d35be986bb38a180",
- "adminx/modules/Rubrics/Controller.php": "02cd13f57d5d85647bbc79b93a283e273c32327428f787343ab149524269d516",
+ "adminx/modules/Rubrics/Controller.php": "3dcece2c975460c72c3da2e60a9481abc4a528b58e128fbaa52106c48215d336",
"adminx/modules/Rubrics/FieldAdapter.php": "edc4b730499111917360670632573b8d28cb7c192f71e88e8c60c7c8c3e981d1",
"adminx/modules/Rubrics/FieldAdminEditors.php": "d3bb0a8cd7e4b44db93b780ec95c49d3974bb3fc3167110a2f8ef044e921c348",
"adminx/modules/Rubrics/FieldConditionImpact.php": "77600152ef7e6a98cbfcdff1bc638aa6b1a664048bc2b839188b56a1ebf0a735",
@@ -415,21 +438,23 @@
"adminx/modules/Rubrics/FieldEditors/ChoiceFieldEditor.php": "bedce6236a9ca6d0cc6da23589313b9e6694a5b0f804ae85021691d3f8b4e57a",
"adminx/modules/Rubrics/FieldEditors/FieldEditorInterface.php": "fbf290cf1d6304a07c5bb753782d4473682b0cce78c6301c743ef05e19b45fed",
"adminx/modules/Rubrics/FieldEditors/ListFieldEditor.php": "bc1c7ead2ed6666f7861418ed2222166a44870edc5efb401226a5b9d80e549a2",
- "adminx/modules/Rubrics/FieldEditors/MediaFieldEditor.php": "7aa8364d698b6581a191ced8dfe681dc210da499904ca392c5f306d1e1d03414",
+ "adminx/modules/Rubrics/FieldEditors/MediaFieldEditor.php": "4497d64ee4b50126c6003fd5e31997f1ba5e391e08a97c4fb2f07a108f3373af",
"adminx/modules/Rubrics/FieldEditors/RelationFieldEditor.php": "9504549a3b81893ec8a114a286a960450bbd302cb2f8f9af97660c8332f91925",
- "adminx/modules/Rubrics/FieldEditors/SimpleFieldEditor.php": "fb4df66a47e820095d5be0856a77f2217a5b1a69640c89c9791ba984248effd7",
+ "adminx/modules/Rubrics/FieldEditors/SimpleFieldEditor.php": "e961e7ab2bf3a16d6d9131513f11d2d4b9520ecee5572ae7e1b7ae8475d1cacb",
"adminx/modules/Rubrics/FieldTypes.php": "c6cc64b0e1b1fb29b65275d337e5747b9f9f68ca46171e7e7f07129cd27b8409",
- "adminx/modules/Rubrics/Model.php": "0e9c3a8b54fc862b46e4f86a71d899d97ac7caff3c75bb287418c1b63e2e0115",
+ "adminx/modules/Rubrics/Model.php": "ecbf5c3d8d91c3979866c8675754572f40947a72d262713809a01b5ba42e7982",
"adminx/modules/Rubrics/RubricFieldPresets.php": "80109bd9f066f077ad19e48d7c7599e4dbf7b1d0c06c54db9fc921992bca2e77",
"adminx/modules/Rubrics/RubricFieldSetLinks.php": "41cd27e5947680eb184dafba5fd95f707ccad0c20fab2ca948cf882ada0adefe",
"adminx/modules/Rubrics/RubricRevisions.php": "084118e4b55b077244bacc7916674a26b3cbe86a08d2e12084dc05f2798b2090",
"adminx/modules/Rubrics/RubricSchemaImpact.php": "e82f93175f031d5772d9d8fd71541748e505273d1622027615abb80e3a97bd5f",
- "adminx/modules/Rubrics/assets/rubrics.css": "345ca91e59044d21756fe5831aee00bd0ca04c603ab32d5c6a28065eba4fa567",
- "adminx/modules/Rubrics/assets/rubrics.js": "986e6adf8f3bfff0385a70c4889bf05d5af251538c48569b26889a29a500e287",
- "adminx/modules/Rubrics/language/en/interface.xml": "c04706d2f8902cedb2e4c1f00e25c995c6e207831ef5295d6c90ef2c64a61468",
- "adminx/modules/Rubrics/language/en/runtime.xml": "659663c346f6fa58996dc60b852ba048d04580b734d2f711af998bcfca2e855a",
- "adminx/modules/Rubrics/language/ru/interface.xml": "72ae7e3db1cc884f61392c70db0c5bd6b0363d8c712c6ad1730fb50ba1cfb05c",
- "adminx/modules/Rubrics/language/ru/runtime.xml": "8cf25cede84b47e321e0b2b586a34dbe8fb6b777c752f8e489bd0dfae0867b15",
+ "adminx/modules/Rubrics/assets/field-sets.css": "fe5253279caa882f9623b058c92ab461c476c080baf2399425e522a36b75336c",
+ "adminx/modules/Rubrics/assets/field-sets.js": "efbb011014f756dd59ac1357441d06c9b69b278a9d934f7cd9d2d1d7398cd313",
+ "adminx/modules/Rubrics/assets/rubrics.css": "fbf07a777744d18d3653c00ca2e1177f4fe685f2b07b00e3bdf251db5ea1273d",
+ "adminx/modules/Rubrics/assets/rubrics.js": "d23435b0c05cc15e3fb6a18e5e1e7cdeabb8d3595f5437a6ea95a4e066408113",
+ "adminx/modules/Rubrics/language/en/interface.xml": "e9f7ec72f954e35c81838e982ae58580ee0922b0a34b1885119db931366945ba",
+ "adminx/modules/Rubrics/language/en/runtime.xml": "4cad3d97c6a2695d038286cedc2c4509230c3bc9fa789dba2da9db89e8d9c93d",
+ "adminx/modules/Rubrics/language/ru/interface.xml": "24df4dc3e08f8ac35d261c269e618c4b613eac6585493c58fa180a945016223e",
+ "adminx/modules/Rubrics/language/ru/runtime.xml": "1bf3f8341af4561a4b6b1da98a28b689105285758da0fed959a6222fd83757d1",
"adminx/modules/Rubrics/migrations/001_create_rubrics_tables.sql": "a827a92e9d83361569b7e1e7c65b65cb019d1675a9078e05002de25b3e62c72c",
"adminx/modules/Rubrics/migrations/002_rubric_field_settings.sql": "4406f7ce1a9e9b0a105c4a33588791d41f12a55449baeca2565d5573e79f798d",
"adminx/modules/Rubrics/migrations/003_native_public_document_context.sql": "eb440a23ced2794776fff47a55609d76489af324f14a084ca916a5ecf2ec5b4a",
@@ -443,8 +468,12 @@
"adminx/modules/Rubrics/migrations/011_schema_revisions.sql": "d9a4d98640bc8b24b650009b90781f577b846e6d9f7309b7537ab5139dd61064",
"adminx/modules/Rubrics/migrations/012_linked_field_sets.sql": "595229c2353f911beef5bb04d552080be5e1c8a36f440d9c06ed9400dd33ff10",
"adminx/modules/Rubrics/migrations/013_group_form_conditions.php": "86544b9f2304b2c75917fe49b33da255c7677aedcbc9b76fbe13f9cd65568695",
- "adminx/modules/Rubrics/module.php": "e9e65910b3366c767eb468d5a5b443c7de6b8ebddf7c6b9acdf303d5c98333b7",
- "adminx/modules/Rubrics/view/index.twig": "4b6f220c811c9a1a524ad3f8a67ac5c206a5e8688d4c2053a76a48ec47015857",
+ "adminx/modules/Rubrics/migrations/014_rubric_purpose.php": "2913a1a7bb6f4c5665ad05704dcbbde78c7ab6b75b0ad7643fd00e482af69411",
+ "adminx/modules/Rubrics/migrations/015_merge_directory_permissions.php": "648b3e232e9426f84d69238295515342e42695e46288b901950cde2e8979b0e6",
+ "adminx/modules/Rubrics/module.php": "74392f60d8d43cf91fff4d49faa54f0792eece8949f11c51a5697c0329b6cc9b",
+ "adminx/modules/Rubrics/view/_content_tabs.twig": "e4c0a9b899b5085aeaec2e1793b8eeac4a7619959fffc7ce39151af1250e0d9f",
+ "adminx/modules/Rubrics/view/field-sets.twig": "911dd7cc9546411e1b29724466328c7d2db8db630b2b52d33e8443a066b16810",
+ "adminx/modules/Rubrics/view/index.twig": "4e87c3457cd6f26df601a7e13f701e16227a36034eadec40fec002f92ffc9ce6",
"adminx/modules/Security/Controller.php": "c7f2263f460057191402dacf8948b822f9b33bbacbe94e6e9ad871613147fc86",
"adminx/modules/Security/assets/security.css": "fd8c477d07c38450587153402380040b72fa9a92c60edd532c4b21046cfe375a",
"adminx/modules/Security/assets/security.js": "f270dd0b910b9aa4815680b8d80d66ffb31bc7862f74e628cc60efba4205a99b",
@@ -457,19 +486,19 @@
"adminx/modules/Security/migrations/001_create_ip_blocks.sql": "832aff733e2c25a43164c60a601dba08c21fa29ac9e826568055dcdb65417a76",
"adminx/modules/Security/module.php": "557308dd9dbe114e5c7129e1cfbc06f25ded104e08c473513e2f388ba945198f",
"adminx/modules/Security/view/index.twig": "b70bb298a3e1aed377d31bc6dd6b7937cbdcb71cd7098113b8b026ba9dbfab84",
- "adminx/modules/Settings/Constants.php": "9cb232e8f0356fdf6f8f8d8f428486eddde281b58e486adbbcb276b88993faa7",
+ "adminx/modules/Settings/Constants.php": "03c0af9ffe9c17fe879974c49396567ac0584f2474101715baf6ada847ac3ff4",
"adminx/modules/Settings/Controller.php": "90aff255d936753ab763ec534a097e9988f6c737bd5be2ba1194a7700a372e72",
- "adminx/modules/Settings/Model.php": "93afb7c30aae1ccfe665b148d182e0d2992c043b0731c228689e9060a546a5a7",
- "adminx/modules/Settings/ProductionDiagnostics.php": "a832245666ae4c00ae6969507686a9cb6db4f9152620ab1e21b67cf60d0ac902",
+ "adminx/modules/Settings/Model.php": "9e16004c78a03d39db1bdfd5e23b88b77e8afc7b8f57a2a21997602ace277440",
+ "adminx/modules/Settings/ProductionDiagnostics.php": "41430528b6387ad2df815a3834f3032c96e29a71d099ce68a846dbe942d4acc7",
"adminx/modules/Settings/Schema.php": "7367374ace3e25fd86e5a829ebedb0358ba38bf5be0cfab9b859f1e5d3cf0525",
- "adminx/modules/Settings/assets/settings.css": "91d075a30e57b07f9d9faaefa9d01f06092a6e4cd322b3e14c4881dbd29cb697",
- "adminx/modules/Settings/assets/settings.js": "b2112d9338f82013c48b57926a9f9340dd8453aa354544a9de25036f5ff8a06e",
+ "adminx/modules/Settings/assets/settings.css": "c8207dc25cdad75f5d2b811a4f8b4444dc48df88d9a0982e9d442f810497bacf",
+ "adminx/modules/Settings/assets/settings.js": "4254d8985d73ff4646799eefdb54deae1f094c2373531cfc22d6692e48b9d1a6",
"adminx/modules/Settings/language/en/client.xml": "28cd3896b4574e87d9968ca180bf02ca783181e11448068426f7f23503253acf",
- "adminx/modules/Settings/language/en/interface.xml": "0472feff0bbe3588a2cf49e17fd0f775f9c263add6cf5628a6b4ec48d90c62d7",
- "adminx/modules/Settings/language/en/runtime.xml": "9cf8627efb2fc5a2da3ec42fa0c2467134480ca96d921228a0e5b13b7de59749",
+ "adminx/modules/Settings/language/en/interface.xml": "34ed3455953a328ef0d57e5d62e7d8db552aea380f99016863e216bae582a06b",
+ "adminx/modules/Settings/language/en/runtime.xml": "b1e2613a192d781927379b7732a1c786e8ffe28965010f9be50b525784032ec6",
"adminx/modules/Settings/language/ru/client.xml": "10031d39c8911a075a461acfca9825e20e9d75836f702e41fd864b7af14bf061",
- "adminx/modules/Settings/language/ru/interface.xml": "0851c5623181ffdba24e8438c2609c7c0f32b06e9f06a693194b6a04b01622af",
- "adminx/modules/Settings/language/ru/runtime.xml": "30f6f63cfbb6dcb4cca6343eca154e1ede24f53441b8dd0438770c3b80f57649",
+ "adminx/modules/Settings/language/ru/interface.xml": "a3ca37cc1dd1f5fc62eb247624225faf5aac4c36383f88a376d864a09031978e",
+ "adminx/modules/Settings/language/ru/runtime.xml": "2c8e5e4216239b0cc848b8c7561eec133db2d9e4218f305a43f4216d40c6b1b9",
"adminx/modules/Settings/migrations/001_create_settings_tables.sql": "69d880693f1f4a68b5c917035841cf32de8015229504fbb7676992aa71c1e4fc",
"adminx/modules/Settings/migrations/002_normalize_breadcrumb_separator.sql": "fc9af9771803b47ad3ddc21bc5ef827dffa6fb0325436132d9a63619716ce292",
"adminx/modules/Settings/migrations/003_remove_unused_constants.sql": "20201881c41cd92bef70903deb0b6771d29756a7d60c22f734c57696fa6c7446",
@@ -477,14 +506,14 @@
"adminx/modules/Settings/migrations/005_order_system_navigation.php": "df82c4966f28ff3b7d9cf184013477c60a4ae635345f21809bd58b94310e8957",
"adminx/modules/Settings/migrations/006_create_admin_saved_views.sql": "69cf1cc085a31b2310f82fe55ad6e0abbb6f14707715205e5a53f06334759a99",
"adminx/modules/Settings/module.php": "a754703ca23db15f500d3edaaa912abeb2097c32bf8615b31605f6018c952199",
- "adminx/modules/Settings/view/index.twig": "c21736be16c030c12ca87efe15877419422dae8493c33605e2d1a753a65c1e3e",
+ "adminx/modules/Settings/view/index.twig": "7d64c286f8e580ae51b1529253fd816cdcabf8122a564fcc428ced98bd7b14d6",
"adminx/modules/Templates/Controller.php": "00b1758860293b56b052a8c41f7174d0d97ba1d78e012b80f8e9f29194eb77d4",
"adminx/modules/Templates/Model.php": "4f7a9082f462170533c0c140ef085f6a33111d20061b464864eb86066d50289f",
- "adminx/modules/Templates/Revisions.php": "59ddb9b0e65ce12df10a6c1d370c5988ecfe14307852bc157731f7a13b16e0c8",
+ "adminx/modules/Templates/Revisions.php": "9ed6496fb8e28474028944cccf0f34af98f816374bcb54075c8222fdc9a8864c",
"adminx/modules/Templates/Syntax.php": "a0172c03a4e091f5fcaafde39713189bc341c1cf7ac9ae1eed50c5cec657bd24",
"adminx/modules/Templates/TagRegistry.php": "85344896cf21767c67b19092830e1ee9545b21067f1e43140efe2faf6eca2731",
- "adminx/modules/Templates/assets/templates.css": "083100ac836b6b1cdf3f8e2d3ed9dac08bc4504dd6f8299562df24dd133a50d3",
- "adminx/modules/Templates/assets/templates.js": "6684f9d2c1f3a956fb111035b5390dd62ca2afa79ca75cc0acde84a8715d1f81",
+ "adminx/modules/Templates/assets/templates.css": "fca6f4e7a5deffdc99d4fedff515a59b8e76c8289961971947e10ea384e9221d",
+ "adminx/modules/Templates/assets/templates.js": "5e5ba6534ee1752d7b23154d44626358a83ba209461b3c0623b8f07efc20fbd6",
"adminx/modules/Templates/language/en/client.xml": "d7280440a3c00674ae2db6a2c837a8114fbfd4fa27590b7e0ddd72395fb08887",
"adminx/modules/Templates/language/en/interface.xml": "50d541fc8b9af282413030476e7677ff2fb07ae20168311aa130d37e2b9670f7",
"adminx/modules/Templates/language/en/runtime.xml": "4d9c9214729aadcacb7e062417b8befb2965f3184ca39334e2aef7fd90fb47b5",
@@ -493,12 +522,12 @@
"adminx/modules/Templates/language/ru/runtime.xml": "800d784c94fcee282dc396f38ffeed40595de7adae019ff960f206a7baf23731",
"adminx/modules/Templates/migrations/001_create_templates_tables.sql": "eab8a337e8bd6b21936bfe908d20bb2d21e18e33ceef9df7245c4c0a4bb14259",
"adminx/modules/Templates/migrations/002_create_template_revisions.sql": "d7d05e99795ef1d376b16c55f46bf45cabccc76e11695dd95eecfd15e845b542",
- "adminx/modules/Templates/module.php": "0c5740a2494ce0335e5b94a63ed509921e2c3f40e32981a03996dbc9f814ba4f",
+ "adminx/modules/Templates/module.php": "27bc0cac9d0e5ad9c4c84d041a6d19f2c0d3d870c90acc1b3f806ba119d7cf21",
"adminx/modules/Templates/view/index.twig": "7c53cd26dd6bd6fd4af757ed53e239c0ba21578c75aa6cbc575ffb5d0c6db454",
"adminx/modules/Themes/Controller.php": "85af532e079d799c0bad87921b99587c946fe92ec073b4016eea650e5c4ef12d",
"adminx/modules/Themes/Model.php": "e291c6d4bbd964e7086180e7329df2867e2748c8f007596a95c88a954159e320",
"adminx/modules/Themes/Revisions.php": "04ee9814b4f0f329ad9c1983333f5f923343bd0179d68897e21102dfea97c9df",
- "adminx/modules/Themes/assets/themes.css": "a7a7e9faf7dd415451ed1e4d1e71da214f33b8b4802259c1654314fda1b79ee1",
+ "adminx/modules/Themes/assets/themes.css": "e4346dcd646702a204a59339b9d6d8ace4be59337343a4b12d72c857a780103c",
"adminx/modules/Themes/assets/themes.js": "6c1b2346f475f97299b8657ca3d867293680c40df929ca467c4c30dc81d76c0e",
"adminx/modules/Themes/language/en/client.xml": "5f9d0a1ca7320e8590468e626433fff92b55cb9ca38d8723d4a363feb77dc3b5",
"adminx/modules/Themes/language/en/interface.xml": "30365f6c02c048fa2d0e94feb82bda45bdf8cc9fa665a7943593bb60636f368a",
@@ -508,10 +537,10 @@
"adminx/modules/Themes/language/ru/runtime.xml": "f8046256e75de65f6f20c8d5b5bd6a927653ca4dc8b6b8f205756b255b624c51",
"adminx/modules/Themes/migrations/001_create_theme_asset_revisions.sql": "21a2ce27d0b97be6493cdb86bffbfb0cb8b16762cc7b0025d83bec71d56a760c",
"adminx/modules/Themes/migrations/002_place_theme_navigation.php": "62a2f2953f3d5bb8df7e39c83a1de83a56008952540c8c28c6a12f33a8fd7f95",
- "adminx/modules/Themes/module.php": "2f4ca527a426bd8973ff2f3070404c350ad8e860094544d071f807e46b2e56cf",
+ "adminx/modules/Themes/module.php": "d5fce96f5841882714e8f540369568bc3c1676313d1a2ad659b3efa96c71189c",
"adminx/modules/Themes/view/index.twig": "68a06ac9ee43b0052c877e89c411a1d149f212418def78e44a1f4a73d80344a9",
"adminx/modules/Updates/Controller.php": "1aef3e6cec043e4e66ab862b4f0c7081d452bc8a1295fc125860eb0b02fa89ce",
- "adminx/modules/Updates/assets/updates.css": "bbd75e2ae0b6b1fc66a69b13cfd6a7244fc02dbf90895e33bdcd1340b0b0b815",
+ "adminx/modules/Updates/assets/updates.css": "53cc7af1afac5910ffd0c11be847215fa53ff95b9a1efb140e5a5f3f6c48d9ec",
"adminx/modules/Updates/assets/updates.js": "c3788da4b31718e297fefcc47e740f20be10342f3c637aa0a7c4678ab1bcd920",
"adminx/modules/Updates/language/en/client.xml": "d1f09120fed6c918cbbd4df166627bc44579d530f61c77c753cf147582ce3399",
"adminx/modules/Updates/language/en/interface.xml": "a9ac07a22f1e021eef53690adb1396efbf6f2b2c2f75948f48cd31e77c2acfd0",
@@ -521,34 +550,36 @@
"adminx/modules/Updates/language/ru/runtime.xml": "45d622ea8c6f772d5f53690bd7c08beb55efcbd51c7308f0336c995885b68258",
"adminx/modules/Updates/module.php": "c26612bb4383e4433543a9e822bc18360b57815af67875b29434bfdc8e263dd2",
"adminx/modules/Updates/view/index.twig": "8d975051ca96fd99902714bd60b6a85131d6304578ea65a3dbf732523b638ece",
- "adminx/modules/Users/Controller.php": "a2c3808077662612bfafbb80430a0a7a028e80d6d9300c1c7079067b5993d5ac",
- "adminx/modules/Users/Model.php": "0cfff6474cfef89cbac69e9f4804c9347eec23eb7435a3fb45f7b033c3c6f6c8",
- "adminx/modules/Users/assets/users.css": "98853d9eb68c8d4b99250d456185a30cbb41437254ab5f35113fdf4aa850ccd6",
+ "adminx/modules/Users/Controller.php": "ed5728b5794d250fcbec2e2bc83bf7812a4279e028949b2373ed83f7873d72d3",
+ "adminx/modules/Users/Model.php": "9937eca84e631dbb3e256b7ab100d6120ddc853e66a065218377a33b303f2241",
+ "adminx/modules/Users/assets/users.css": "19b53f5a235b79b5ff34576692ace47cab69e85586958f763118f59a07b2920d",
"adminx/modules/Users/assets/users.js": "a9d7bd3b5b2e44988ac1c5d270d44f896b7cf1796608961caa0d8129508d11bb",
"adminx/modules/Users/language/en/client.xml": "d1d17febaf2fbc74243f0e799bae71b32c656ceed81da7fc62f13dbf73ca7b4e",
- "adminx/modules/Users/language/en/interface.xml": "ae023172c71b0519bb2aabdcf8a7a162ec4c45585182a3da653916e95c450fca",
+ "adminx/modules/Users/language/en/interface.xml": "28db6106985d83555542a171a8a59b06cfc0797251ad9a533fd4796be5eb4c56",
"adminx/modules/Users/language/en/runtime.xml": "b68f556143021c97fd369813ee0fcdfe66af5febcc74ed757b53605b2b797b10",
"adminx/modules/Users/language/ru/client.xml": "69d389f961f82692717c4bfdb6057700c23f09e226410108053ec9f1574295f9",
- "adminx/modules/Users/language/ru/interface.xml": "ceb48f73f3e11c1bfb8ad0aa5716012c6331feefe9bad3809afba8f28a64785a",
+ "adminx/modules/Users/language/ru/interface.xml": "3129fdc122cf197452954b328192ef8a042b085b540f572de86fc5b239e7e462",
"adminx/modules/Users/language/ru/runtime.xml": "00c74dd13ee0fb0a578f0465ebd74a9c2b87d43399ee0e22ddbc911df2043b6c",
"adminx/modules/Users/migrations/001_normalize_public_session_activity.sql": "e72f1cacea2cff5f74f7baebe3cb1f154b87fd7368541b1a3a5d2300a0deae86",
"adminx/modules/Users/migrations/002_harden_public_remember_tokens.php": "85c92acd3ed962dfc4bf85b78e07de87569b1f0aacb2c616287c95bb39fbee2a",
- "adminx/modules/Users/module.php": "9df770b412405038bc0539722f369b801cb9b6d7ca9ea1e60218e11b101447a3",
- "adminx/modules/Users/view/list.twig": "ae771b2dcd7f374ad6c2a3fbdbbb115a0ba0f4ee382ce5ca43990e911ae5411a",
+ "adminx/modules/Users/module.php": "81c13d81fd73878aaa372590b0ed5f2a8c19ec59d409c5199a304271b1011746",
+ "adminx/modules/Users/view/list.twig": "231abe6ddb6324a7573f2da75e1a75e29dd7dfbd4ed010a3b38a5cfa313d8ae2",
"adminx/view/404.twig": "86f93ca1cb44b0c225a03a33704b903f8a96e1697a05d40b0b71dedb8f665769",
"adminx/view/_pagination.twig": "9d5e761594ae97d7cb7b518592028adfd2041b5d99f736b24a318b2766e350dd",
"adminx/view/_saved_views.twig": "51217212a3b0ef7437de4dd55a07fdf55aaa3138155b00f65fd85af686412da1",
"adminx/view/auth.twig": "e44eb6337248470418be7015886e6e5d7bf5b0021fc917f4cc0dcd98c6492d85",
"adminx/view/error.twig": "1187e6d0c325e0bbf6a7bd9836a5ef6d96cb8f72a0599050d3da7120683a6202",
- "adminx/view/main.twig": "2ec8f2ffd5d6d3f2fd45027ab30cc9c8a611b0dd4e0c5baa081fce8021a3fb43",
+ "adminx/view/main.twig": "4aa00f5a2c50e3cc4d339c0ad9377526db6e8a65575843be59dfd20df5210344",
"configs/db.config.example.php": "e42bdb47aa7d1bc9cd85cb36e05acf940cafd951d6220664c485d1b971f18809",
"configs/index.php": "48e6b249e3d83530187c095ede0198af498c59f384bd458db74f9e0c2306eba9",
"configs/public.config.php": "7047f7acfb9f64e56c7320c3cdc306522a187f5f9e12cef6e95937446ee07441",
"help/README.md": "d12965e7851dec5241dd74db731c110a7e0f4acfd7139ae28ce21d53193b1123",
- "help/administration/README.md": "a6658aa4a593abcb9f6df35edc4781529a518438bffb8fca77ba5502a161d294",
+ "help/administration/README.md": "b07c99046a52b6b30cf8e50ee00a33137087771ca486b8c4e365a8844096850b",
"help/administration/console.md": "5efb2adbee857eaf0b8c2816ef1556addb2c9b21420bf972c6bdf62911fa9ad4",
+ "help/administration/content-and-system-tools.md": "55747a150dcf00cb3682d30b1edcf054201eb051d0e25274513ad41716a9f392",
"help/administration/dashboard.md": "98a93834bb648b4b94fead19d31c371d988013028ece38caf9d4b8d455628e93",
"help/administration/operations.md": "4c585d6c3236f11656000332ff99c066187eb36cd860852c7fc857020487c05a",
+ "help/administration/search-and-activity.md": "f0f470ab432ff344b176699393574acc2c9a9d153b2d71986db9201bea374482",
"help/administration/settings.md": "89ff256f830a827929847d0d30662e1af0a2cefcf7bff214d6875d7c14a164da",
"help/administration/updates.md": "1fe89c7ab6a1cac896bcbf447e130acc3792c7c93b3d2b063160dda32e1b9ca8",
"help/administration/users.md": "4f035c98f136e7a3b794d3634c2d22809bfb07b3be3f8ddf3d403ae07d96c14e",
@@ -556,19 +587,23 @@
"help/api/authentication.md": "71f289056bbcd688c2ed19c1af43e4b233db92d76760b95ac9e63efddea99c08",
"help/api/documents.md": "717101e0fedee8ddd987d499a9ceffb19b4d24e0a04481aef72cc6330e88fa43",
"help/api/errors.md": "630270fb28d5d385e368f3b0cc1c5cc2acb3c56607bb8c9af62c2e62a6d09993",
- "help/content/README.md": "b52c0cfe8e5d6c114a33eb2c6d2452a0a36b5335e4f76061375d0aed4712bb17",
+ "help/content/README.md": "3278c036ddadf187cf1fb0648682f6a1acefdf5d1d95c4960692a35fefe8cf99",
"help/content/blocks.md": "1d55be2a9f1a48df426e5856a54307d77b352d0037d68f08ed7e16a322d9ef8c",
- "help/content/catalog.md": "8f40be9562c8031ba035043d16f744cf625c5f761c7896a4180e1e751c8e0a81",
- "help/content/content-studio.md": "5d200853c3601be6ef8b4c82fe98d1bb7e82b9e28e562fe4857d06f6aa226c81",
+ "help/content/catalog.md": "5ccfe015ac45bed261bdae480a87704142b41c772c7bb8c425363a1dc06fc6d9",
+ "help/content/content-model-tools.md": "baaf738aec454b1855ea110b8f5e1ef3ceaaad51cb920d4551cd088daeab7e76",
+ "help/content/content-studio.md": "91ef485e614a9a71a07d4e6c143377f7052ddf3b00d3f2f0d0a32258bdb14dbe",
+ "help/content/directories.md": "ff6c5ea9bd7fe1fb9d0aa1875d03fc3bc00b2bd2b0ec83d6496cd55168db294c",
"help/content/documents.md": "e5d7f51af5488cf3363326697bc5dec56f377b45963fcd6a029a354871661afd",
"help/content/navigation.md": "a02d0f88b5a3aa67fd14a2c26a48c9c3c4bb73f4824fa453c20f3e2ef4ef24b8",
- "help/content/requests.md": "6487e6c6a41b2cda89912dc535b279a1562bd6514b8f0a48037616fb32bfc1e3",
- "help/content/rubrics.md": "556805ddb3216f0b3659337b86c03d72274962ebab0bda082f4b8d31c54661e4",
+ "help/content/presentations.md": "6e349bb4d1d1577617e6bf3d987b86ca6b255f64a47cd1ddb0e38c5242b788cb",
+ "help/content/public-site.md": "3abb9bc9c78059d22e8a75cc4d01220c0b9558da4a1b2c8b342bc26c8e353d04",
+ "help/content/requests.md": "c008b1026520c85400fb9240cb117fcaf384531c06e859c1e36ccbb3a79a8590",
+ "help/content/rubrics.md": "2c9842ce5647fc85663b02cef14f141c673694f0a20856c9262b6ccbea436c3c",
"help/content/templates.md": "c6fae2ef91c741479d1f34fe66d00e83f2d2ec1aba1a4099773a04a6e1b6552c",
- "help/content/themes.md": "439b9dceb6b49a66c05c94804d06a37d8abd18ed4c26660c3234c6bc35a2a620",
- "help/content/twig-components.md": "cc977a81090acd7cecf0f4550914eda716701f0b93eb5988f92b7738964bb166",
+ "help/content/themes.md": "f522107e2ee27590a40d726f26538a3101925d0d0173fa1e58422089b5418388",
+ "help/content/twig-components.md": "15e40ce305f4416eff6ede5168be32c033f712776484fb6c72fead9a0d7d70e1",
"help/core/README.md": "c8c42e4915e049e2821cbb548a496bd4caaa9c7921a205db5d7ddaa9445ace42",
- "help/core/auth.md": "acc76eb4b00f07a6d54b6482e8f18c2aaedd304290acc6bfa2d73aad130a739e",
+ "help/core/auth.md": "da714664fb7848676229a9695d49c811786165a79de7fda4b7ff0156a52eff6a",
"help/core/bootstrap.md": "72b6759b1066e2906dc855dac13880e0227c1d70f751f81365c6afae2cf919f7",
"help/core/controllers.md": "6767a22647bd06b7af5bfee2a9268bce95d67baaf3f4a828b88233def1e4c665",
"help/core/permissions.md": "3147cfd90fd1139961822505e611c444e54bea4ab66b86f1c0da121301fcc57a",
@@ -586,10 +621,11 @@
"help/debug/README.md": "41b7954c443330efe28cd3a14519c7c8076165a9d96c0dc3d3781a111e757085",
"help/debug/custom-data.md": "445ed27dbda5b0ca035504e2f0cda023746def5edd3aa1f62189e133946e293b",
"help/en/README.md": "cdf84634ac731f1f434ec496b171307c38fccd6e6df335dd4dbf074e3e2cc12e",
- "help/en/administration/README.md": "fadfb46c1f45d3d7afbbd6e323221b099fb9b338cf3c17bcb8826b26a67b9372",
+ "help/en/administration/README.md": "5482951c74022a90b44f70bf8c47ed4499b95eeabd79bd99edf33f89cfa06805",
"help/en/administration/console.md": "0938f6598c01381d79235a6b524d34f0f9a35fa37a35e7042baf04cf4f4c195e",
"help/en/administration/dashboard.md": "1945cb5191944f41a88e5a59eb91f1baef550f9a1d751817b08be5112d23d122",
"help/en/administration/operations.md": "cea8833c931040aa0d00ba0a181270cf95b6ca95f199b6c250c0c8b839b86b97",
+ "help/en/administration/search-and-activity.md": "65f3df81100e462eb16f17a4d07c8106156a99166cc4bfe18fea2e078318a669",
"help/en/administration/settings.md": "68586a8a8a397d9b5f76d52867f5a1ea0247ee47087faec22802b2f8c4c2c755",
"help/en/administration/updates.md": "a0ec3d0929602c59935836b5e03e35679b86058bc482dcb37475f892262401be",
"help/en/administration/users.md": "018541f3fae298feb40f0c162bf813b2ef0ee4e782c7067dc5c4efb207177db4",
@@ -597,19 +633,23 @@
"help/en/api/authentication.md": "23ef6699d3082373dfff77253a79ac5179c0088d3f69e1d1145c8cc7e05efa25",
"help/en/api/documents.md": "266d2cf298bfffd62a02289b97bc01be508d6dcb71d0a8b0b3bf840afa5307c1",
"help/en/api/errors.md": "4c8ca63333fa082aa56cff33c1ad3659bc8030524e490a2f43d122ac4ef68309",
- "help/en/content/README.md": "4da5eeeca08496e4f4d2fd8634da5ff021c24744b70b6a16d646dc0a8a644b9e",
+ "help/en/content/README.md": "b88e329102be722abbf1b8a9e99d90ad960e79ec73fe03b1854276c92d5fc094",
"help/en/content/blocks.md": "34657f0a7ab977bf78d5b7645bf80a0755f40830bab4d4620ca7f39ea5992210",
- "help/en/content/catalog.md": "b22e818f4fc9f7d683df31c6357dd97a3eca9834b8c5cb1e9b0d80f240280668",
- "help/en/content/content-studio.md": "746a9f538d951f7f53de0e80db9f08e3bac0351b39ad286177b1ebcb04d3a76b",
+ "help/en/content/catalog.md": "d4139c69df31e32b4c37308f96d539020d989f952f75b9f601565303cf2aee48",
+ "help/en/content/content-model-tools.md": "fc50f8c334a129d3ead4a58503a7659abc2b1fdea7c9d277feff1e3ba4bb7b6b",
+ "help/en/content/content-studio.md": "194fe41a1eefdf7da3c9a47b8539ed3ed9d785d2a75816a39da6d2784ec40925",
+ "help/en/content/directories.md": "cfadf2361ead3bed9b63435041af8c91e24ec8b492906f0d9b976047edc123fb",
"help/en/content/documents.md": "369712175c276d241ab5545ff35c2df56e62d0445c4506ff6b7610d9def035b0",
"help/en/content/navigation.md": "18f6e033c5c01eecbd7a3bc4b7c557f272f0039c811ea6f4ec2354206b0a5f21",
- "help/en/content/requests.md": "152b82072cc9276482bf6921a607b7a5564d211644d3ebba66083c8554fd2845",
- "help/en/content/rubrics.md": "e933018a487059b7d1a867c08c1f34fa2f3a40a68dec60b1bbfb492cc9d5308c",
+ "help/en/content/presentations.md": "f255470ae47453d2e458b1829316c0e1e8e7fae73c9a1c1190d89f522b78d871",
+ "help/en/content/public-site.md": "ab639977029d10f8a88679b524c771ad6437c49e4e061b8f95a4ade0188efcc4",
+ "help/en/content/requests.md": "71aea0d1fa6dc9b6f6dd41f1c7a4d80af93c0b5e18341dc26f59d1f0c156604b",
+ "help/en/content/rubrics.md": "bd69370ac19ab0aaf6d4c1cba615ae21f57cc3e241ecb6706f23a69a794fb3c0",
"help/en/content/templates.md": "f91c141dfee841a1af8321625788cecb9dabaef75901e188127815b6d5b5ce6b",
- "help/en/content/themes.md": "e9d0d80383c02840480410224b84cb14b097e3b30124b47f600e2c6c569142ac",
- "help/en/content/twig-components.md": "c1e100c88678c044245f213b734efba232fe30b623ba8ccd34be9399d3098809",
+ "help/en/content/themes.md": "b502abddb9114cc6cfb990e75c34fbf970c4e9c95940f27bddbd5fec80747b0c",
+ "help/en/content/twig-components.md": "5fee2a73c795f96168ae8bcccbc4dabcad3c82a0adce69a2e2add878dafa033a",
"help/en/core/README.md": "ce0c4d0757cec5786cd79fdeb8a839250a947d87e2b5a5905f7a8010db78df6e",
- "help/en/core/auth.md": "235ae7b48e66e6c8893650d5a2225e12a02e24871977099eaa41acff7d74192a",
+ "help/en/core/auth.md": "1fc1e444627669e1c2d0b5b15e363bd69dbfd18085783695229015213a9a0cc5",
"help/en/core/bootstrap.md": "e2c7b022ccf6bf9dd2e888d3cde42bea82d36be382d8d0cbd508d3facee43f7b",
"help/en/core/controllers.md": "968107531f9bd1a714a62d9f390704f29136fb531f1b72a36eaa72cb0cb76952",
"help/en/core/permissions.md": "6e6f1d5d1c20c85f6e3d71aa3f4a7de8e822ca08358aae38981bc7f7082b13ec",
@@ -629,7 +669,7 @@
"help/en/fields/README.md": "46c4fb2b44f1fef2f25ed2be3c730dd1d4e1aa0f48950e6069c3772acdcc5b17",
"help/en/fields/development.md": "4a953779ec8f08a79e235bd658d58ebc69f5758e2d8c55d49d5bc074e5aa0a41",
"help/en/fields/operations.md": "7f3e2e991df10e5bc8bee9bc7251b9bd96e5137aaffc5142401f80afadd752aa",
- "help/en/fields/settings.md": "a3556e6bea0982f91153926b0096518f299f341935f188fad4a3934f651b2f2c",
+ "help/en/fields/settings.md": "577d87c57c76fd959e8eeb6a9e25060ff82d43846eecc5a02f95a700872743d5",
"help/en/fields/storage.md": "2e8d0faf13e79f508e6f91e78c3b988811d3c39379c1fa79de441e06a44ff64d",
"help/en/fields/templates.md": "634dfc6e4f14b3beb5c84cdfaf5d2371535c51dd464638474ae5b67a4dba0f8a",
"help/en/fields/types.md": "db8451912c4910fe0bc416cdfcf23f16a8978dbfe83f481f887e2fd3f3832bb0",
@@ -661,24 +701,25 @@
"help/en/helpers/Valid.md": "739d5da3989b3d49bf958f670e5e3670dad143058433b616da0f17af276e09ff",
"help/en/helpers/Zip.md": "e77e4a66c0c4373b25d665d7e95eae1d5a0af4bb1589f5efb8985622231ba13a",
"help/en/hooks/README.md": "92ee41f7b9b9daba4c1bde7099bfd221dd3499efde9d8f3f9b56ffdbad0cfd9a",
- "help/en/hooks/catalog.md": "4541de3babb6f5a6dcdd8c495bdb0adfd6ab6036ed5bbbbb4bb3e55e06ab9b12",
+ "help/en/hooks/catalog.md": "44f5d6d1a8d197a5cf2de8e3ce7084533b3919a27151b7d05fd5beeb0e085999",
"help/en/hooks/debugging.md": "910e07cbe4a802efdc7ce439eb9eb0e6231d667ee527664711bcc8e863e0bb34",
- "help/en/hooks/documents.md": "94d713a991561c8fb7642c4bea1efc491d9255aa3c6c867006ebfb89cb270e4f",
+ "help/en/hooks/documents.md": "381f0d983cc2367abb99a2604960f2443ab17234344350fed22b2064b4629067",
"help/en/hooks/fields.md": "4fa1dce004be2b1cac106fcde0959908880d2e8992be9df0e5b981aa166672c6",
"help/en/hooks/lifecycle.md": "ec82cd9ccb2bc48e9054ec1876c40552859da4ead45129f920026f821f84e87f",
"help/en/installation/README.md": "a122a89fc4a910064f02fc906a0fdf3a9098147f9ed2f98a75185ed66d190f29",
"help/en/installation/admin-directory.md": "26a7b2198078f8ea11dba7fa6a0699cf35c66e78fc88279c82012e6a01f59a38",
"help/en/media/README.md": "cb9abeaed976e18cd7b294f7c078b7cfbda339e0022853d3656c7a29b7fed13e",
- "help/en/modules/README.md": "103e48b81c47f8b545df1563a0bb6726aed73d1f2d3c7b9f5960121ea61522eb",
+ "help/en/modules/README.md": "52a6ba0fc3d163c4912b28021b272977f47a3f0120a906f8e8c614f143950a47",
"help/en/modules/antispam.md": "1808f910d530f7068e60e2a9306c15ae5b69cccc30ee3e492f35cd535004f65d",
"help/en/modules/banners.md": "b6046d0094b0554c31b56dd15ea299e247f5f3dc13c8ca8fce5714bb6c3193cd",
"help/en/modules/benchmark.md": "e7b9d233abd386db41413f45c79639a3bebfc37241bdbab461028e87a5615f87",
+ "help/en/modules/cache-warmup.md": "9404771e435f1130fa6812ba7c444e21933f1e47487ba7f1e96e8992db1d224a",
"help/en/modules/comments.md": "854896934b2d93d6527f9ede94e85b5e9aecbc282fdaeb9572f810c7dd773dba",
- "help/en/modules/commerce.md": "d3ce1e24e4d719adc9e1ada79dcc562205f7833d88b9944b5113c9abf7ce79cd",
- "help/en/modules/commerceml.md": "250c24aa6b88a814930960e79350d88d78407e56fc564677430ab7e216befae7",
+ "help/en/modules/commerce.md": "38ae79ce7675efae05a55c9425a1f61ad966556cc8cbccf3355087d0a9d6e96b",
+ "help/en/modules/commerceml.md": "610f6b2f9abbeeac604da0408192ae52dae7d6f3fb09e0d321e14d1cc190b808",
"help/en/modules/contacts.md": "3d4d7b109a7a7bf20ceda0cb0da65634131a837c7d39c4a1433f317c3bad3512",
"help/en/modules/content-packages.md": "4b89df6d05fa75e484e38e404d53fa7ecc6b61aa6930fbf74cf9b6611bfbf532",
- "help/en/modules/contributions.md": "ea01a8cdd61e5df55e45353ae1c05804095646b2d33e70704fc6abc8722f294f",
+ "help/en/modules/contributions.md": "ec283e61f09d6008a7208fced495a2748685c50bbfe22b384f458cf338e63125",
"help/en/modules/dashboard-widgets.md": "363ebd63b5d28c23f4be2e3cded9e690db78cb0b79c459217eced1151e2b6a8f",
"help/en/modules/demo-site.md": "d8ccb9d14f664b905c89d81fd046116d1b6fb45f2c0105ff2876d5c19b16f57e",
"help/en/modules/document-import.md": "cf0726b5cf95b6549ae6732ea37ce127ef3a1fe3b70109d83d74f5f48e4cf0f7",
@@ -690,8 +731,9 @@
"help/en/modules/header-actions.md": "d5126af7516e5fbc71eb8bc18b20fe00b20e25b987c285df0119578dc7a8628a",
"help/en/modules/help-viewer.md": "50411de1d2069be54fdb6aac44285bf860f1bff61b903a2f4328d82b747598d7",
"help/en/modules/interactions.md": "3cb36ab97d91ab3f2ea1400259cbcd5072fc140a9c2b0295583feadad566c7af",
- "help/en/modules/legacy-migration.md": "a137a6be5043c0b6c85de052608c624a5ae5b33202638b01d8e733346eee5625",
+ "help/en/modules/legacy-migration.md": "f9dabcd0ca6d69999ef989457a98b6a52ad29b42fc9958079e4b3f203b1bc15e",
"help/en/modules/localization.md": "f84bd43534cb3147f5675908deed2503271e9a96ef1cacca8b3fab17d8917f6c",
+ "help/en/modules/mcp.md": "98afdfe8128282672e6915159e39f6b1337b295022d9091c392c5a76d41ae239",
"help/en/modules/migrations.md": "5a2d09f1252ad7c4670d06db1a1dd029593856fabd219005bfc0d9891bd6c2f2",
"help/en/modules/module-php.md": "d61b3a47097623c1d969b716d78d08939f446980648b0718c1dcef45b7af8f36",
"help/en/modules/not-found.md": "77ab48c7428a8f053938a780986d9c2d435be5c7aebc1d70093574776436c691",
@@ -703,13 +745,14 @@
"help/en/modules/polls.md": "a8f86b04024cfcf490e9ee2f5dc2e4293ec088cdcc48dce11c074b02310c1180",
"help/en/modules/popups.md": "46a2d6171349d186cfd18af984c7a65dc1184f62406a700767233f3176e789ea",
"help/en/modules/ratings.md": "37fdf968a33dab9224ede4f71bf093a8d9121613574961341fc0e436a2bd321a",
- "help/en/modules/related.md": "1bacc84e67364da53fba3a215c2b2337aa86f7bb75174b20a8bd6576d54d4ccf",
+ "help/en/modules/related.md": "863982d9102efb874dbd362107bee773780b7c319a78b544a79008dd74fc64a3",
+ "help/en/modules/reliable-events.md": "7069a39f950f0e5688b09633c840e3be73b0b4c244b89115016e357aeac393c8",
"help/en/modules/repository.md": "1f835de827a64b04d6c00a18c060fbd6c414a7367daf8f10e9b6c6106dcaacb2",
"help/en/modules/reviews.md": "0175bd201536c306b2583858a248740bf0445cb39b5ffe37dc76a9f6a306c0bd",
"help/en/modules/rss.md": "22958cb500ddfcd83201086b36e43365d51f9f125b8663ec38e2e30eded4d0ae",
- "help/en/modules/scheduler.md": "461c67e31e392c90bf60a6e0d9d55c65719a15183cc5454916305b9c4d0af4bd",
+ "help/en/modules/scheduler.md": "fc0c34af054e0081c6ad8cb1abddcff014c0a6eb77178c111fb0b15e0585c75f",
"help/en/modules/search-analytics.md": "3e21c935d76171a5a74cba9fc2904092d2d30062918c2f7ff1988aae0e499264",
- "help/en/modules/search.md": "a3e323119de8f3783c758db745ff28483feeb93685cd8e998771ccc119e18212",
+ "help/en/modules/search.md": "973ff0a67f7ccece663d74c2a92c177869ac3fdd15484aa8c2d602c4fc5b039b",
"help/en/modules/seo-audit.md": "e18adcb79847fb5a1cedc8cfff03b5938eac1c2e2d22816ecc5e5b02b48b95d3",
"help/en/modules/site-readiness.md": "dbccccf0633f0e72d32cc8a2b0de319dea576f28b67f0d9c2907106c97fef1c1",
"help/en/modules/smsc-auth.md": "789c352d0d9efa5d1c4d88f88b508d925b08518704326613ce35cadd1bb87fb1",
@@ -720,7 +763,7 @@
"help/fields/README.md": "01d359b4ebeac9401be857443c4c16aee73933cd7d6307e0a25c2e3db6f98d5a",
"help/fields/development.md": "4b3de5adc867d34dab7d3a658eefd4deb0622b9c62f1a4fa5a6b0f24ecb264cc",
"help/fields/operations.md": "658f5ba5ff96cc99192d5486275d2ce9f98555cd8c8d43aed9f8f08a14bf5ce1",
- "help/fields/settings.md": "e1216af1489ead6ae4a1fad514aab68afbefdfcef548be11759ef947410547b9",
+ "help/fields/settings.md": "cba0acfc0fd93818e2e13c100026bb46643d4e579964ba9ab6d50fe7675599aa",
"help/fields/storage.md": "502a8b0ab9c22b5e023f71f94cd0960bf00506c9cc2368ad72f54c354595c116",
"help/fields/templates.md": "7538e7df6ee2e7535a106c97c6a3d980678a0f148132d530bfcada9fcad4be7b",
"help/fields/types.md": "3197b168942ad3bdad5bf645cdaf4d54765f52307b6947041a7a255f5225bb60",
@@ -752,28 +795,29 @@
"help/helpers/Valid.md": "384c0c880fafdda2c69dcc6340f09e6e4481c7beb04fb600e568e7b3e3dc6e8a",
"help/helpers/Zip.md": "48966186d2812305d3bb74058e53c454085f52b6422e2a3189768e77531faaf2",
"help/hooks/README.md": "562b3d8c96ffa407fddbb327d51b68d7a60e8be494bd9364a7c3ce14807c5f20",
- "help/hooks/catalog.md": "cc1043d2827f52ced69a559a4e52b6c297365ea8075ee4594ee211edade5833d",
+ "help/hooks/catalog.md": "9c42dfcfc87713e5df9f1214706b4ca82cede4164bf8301c249ac83127877c7b",
"help/hooks/debugging.md": "643ccb475d890220598b6d07387dd2c1a65735e7768c9616a26eaab7a1862407",
- "help/hooks/documents.md": "6380c1674a13ac0d3caeecaca1e3f5019b43dd44e868eb271906d6fe2c0bec07",
+ "help/hooks/documents.md": "a43dd353016067e43083d87f6051363204e71d406d7e71070c58c7b417e7d9fa",
"help/hooks/fields.md": "62737f4bfcced38ca8089267fccccd2a5769551f79cd884b496d40f04e9ffa87",
"help/hooks/lifecycle.md": "0d73b7eea62dbc1cc5f3a07449f465105ad8a477adf85b22d2adbc53529b81d2",
"help/installation/README.md": "6a9c434bbe81f493ad3b07ad83fd8c8a8739423f67f3e657b5a9a3dba0d2738a",
"help/installation/admin-directory.md": "fcb31d8bad1c18dd7e5c6843dc4f7b8c4139dbceaa670a7c4e0c060479f5ef9d",
"help/media/README.md": "2693af05de4dc54b280427607dfa5824e3098944b93ba9b92f76715060b9864d",
- "help/modules/README.md": "b901471d9439915bcefa728957aa20ad351e3fe5753f8cc62b43013e0cbbd753",
+ "help/modules/README.md": "a232a6e666b9ceea49a140b1a9d896cdf9e777581c6dc6ce287967e97c0bdbcb",
"help/modules/antispam.md": "ddcb858c6c80ee140addbdcd0e27ae3f7900100f1745ee006edabf52d0955fff",
"help/modules/banners.md": "e8faa2a923c5f5c39a0b7caf80a511d03fb680bd2604ddc59ec0362a244ee77f",
"help/modules/benchmark.md": "6c31f16fc3f083f29c65398c38b78609a3d94da168d30a93825af8ee9650eb0d",
+ "help/modules/cache-warmup.md": "3f3ac8252b0f808ccba046daa69fde3bbb438b35252a16fa47255f5624e0f19b",
"help/modules/comments.md": "a9b9a4ff122af3a2842b2e85d007973af935a07ce721f933ddaabe1d07e0e1a9",
- "help/modules/commerce.md": "21b0f353294be599d47647e539aa5760440ef2e7fd2b4efab80c4c02490446ed",
- "help/modules/commerceml.md": "c74a3408799c72996c86dd86fc608cc9ab49467f5fa938a6f2e5ad1568085d18",
+ "help/modules/commerce.md": "de7b8fd3c27516044060ebfd96152f428a35851f2dfee62fb4edd10ee09f2ff0",
+ "help/modules/commerceml.md": "60a0a5c541b560b5a6a4ed8213d9145caace68c15be534af13f5f817eb5251ba",
"help/modules/contacts.md": "8df21307a300a335aaf243cb42a107829114ac031da128cfdfc6f4b94452dd3a",
"help/modules/content-packages.md": "8dde0ebc897b7891a6afadb6491f4797bef26622918addab67f0f4ebfd719678",
- "help/modules/contributions.md": "70489671ec9c74b3ac2933b1f70982cf0fe59dc69fc91c667a70ad1d3783b27b",
+ "help/modules/contributions.md": "3213f68af10e1b6a200aa61a3aaa9dad270da7ccd675b43bf7cc1ded90b55e60",
"help/modules/dashboard-widgets.md": "db967aa0dc13d9c72631e219b6b3769ffc350211eb0ed86a467e5a3baa1462d0",
"help/modules/demo-site.md": "6dcb58f90c00d921ab1ab097424ea27845079d810f026a128f3ff9ea190982cd",
"help/modules/document-import.md": "579d5270328a962c0d171cfebef07a9709153c2a992ca22b8f07637dfc2b3c91",
- "help/modules/experiments.md": "aa42fb997906eb5e2e15f8d5ba61fd8c93e7f9368ac10f042d9917a2364120b7",
+ "help/modules/experiments.md": "923c3656188192e0cfde640d20c5e0a4aa6add4840e5dc0b747583b105e14256",
"help/modules/faq.md": "ac536c5fdb5269f54f55475319d351c27993c37609769c92025a2e00be5c8c0e",
"help/modules/file-security.md": "5e8fc01b5c1f8b73f117f1ecb305e2ee642db92edbbe932cfb416b16c7ff705d",
"help/modules/files.md": "86305ab6e33640990d36f03ea39ba319d2984aba0bd7f300cb035ef497e94aaf",
@@ -781,8 +825,9 @@
"help/modules/header-actions.md": "134cdd5cf4f8b1ef59c22b971b3d4faaf3ef4c493385100481e7e852d3e163ad",
"help/modules/help-viewer.md": "175db88e150ee02dadd8b841ea74262490b8211c67bc42bf05cf1dbafba1a25f",
"help/modules/interactions.md": "6c62a4abb3422609265b1898cb448dd6bd840edeec98f30fb0062c6d18808e18",
- "help/modules/legacy-migration.md": "f3025bed1df1af4dc3ae99206681e26c5fa00a57fea54e4fd770d502e5304dea",
+ "help/modules/legacy-migration.md": "773acd5026409522bdd3a0d3fbbfcd915ca54849d64c97dbc0cbfc2a79fce36e",
"help/modules/localization.md": "f6a58cba016a55c7a9a88c594ae25fc79828411dae13a70aa84a54860821ae43",
+ "help/modules/mcp.md": "a7b63a32773a1517de3c5bc328771bb3e2b9ff402f1004ededac2f94add25b02",
"help/modules/migrations.md": "e144ba009df225508d5f73e9ce71e17d4a379f9ddff49e640c2c1234e389588f",
"help/modules/module-php.md": "3594f19c10381b4d450b6ce5b9b1d83b2f413067d67f7cca334d3a14cdbf2a0e",
"help/modules/not-found.md": "50e45e048e07e27190b8583928c8fe20cd77c5e3401adfc47b9b2effa1ccd799",
@@ -794,13 +839,14 @@
"help/modules/polls.md": "4d13eabeb2a546ff036c8095cecd3e8a6781ecc0de7446b27c4b26b152397ee8",
"help/modules/popups.md": "f95ab2440beafdce8801aebae32070e3b4d4f04148bcc9e95a0aa3231ac96411",
"help/modules/ratings.md": "e4c8da2672ccec5ce085c507919fd016ef204427ce919f1a9c2b1c0146fe7c8c",
- "help/modules/related.md": "007631556df983656542bf039cad79426bebc61ca2eaed2a90750e2f0954a220",
+ "help/modules/related.md": "5500f2aa6a7c95367e439565b14037d2b73ef4218ea0a824088b3f32979c8979",
+ "help/modules/reliable-events.md": "983074f9490bc5897acd0373e8c1c748a127ba54ea1248a4e3675caddfe7e4a5",
"help/modules/repository.md": "e371ac47aaf81408242408d795df6cfdf62cbaffa232192cc4cff7dfb5e6c5ff",
"help/modules/reviews.md": "a16bba5fee4d17f6fe0813f54c168fc98b4fc30050a349efd34a21b7b757b2b6",
"help/modules/rss.md": "0f8fe5fa5bd4991b432068a5a520d4a45d41dd6a2c6011f7f293f74c8d9c1588",
- "help/modules/scheduler.md": "96272f91cbb17b5fd12b463e1f5909bc825a226b0fd53e1a8e728c066fc97d07",
+ "help/modules/scheduler.md": "a39cf392ce9cf5af63e92063b95aea63979a24b27218a4b090b94834ef90b536",
"help/modules/search-analytics.md": "0444e0dacabe408358bcdfda4b6b2a475095e8b1c0ffc1361ff58c4db0576d35",
- "help/modules/search.md": "ff25a35ff67a27e4ea59baaa99a78826cc91d518c93ec44f690f405532ea6007",
+ "help/modules/search.md": "c1a130e487de27719a8171590e69fef6bf9e85136ae4d3ab862a380826c62e55",
"help/modules/seo-audit.md": "b0fb78eda0e83c0711b7717fcc730e7bc0cb86c6d692f21aa749cc4975f0ee27",
"help/modules/site-readiness.md": "2c7f845ece693ef14f23ea2b3b226f38ebfed305fc67bf842fae971f0e12255a",
"help/modules/smsc-auth.md": "57a5c3686d7267c166edc858deb7144af4d3002eb6b9d01d8f2ceea5a9877e00",
@@ -812,26 +858,26 @@
"license.txt": "b8093fa95967d0f6024a5334bc3119c694aba6f51be2fce66be6939f68c39e93",
"recovery.php": "2ecded66156c70919f9e83451ede4bbc1a5d5e5636e9362b2317ef05f733004f",
"setup/.htaccess": "a25c35a118024ed8b958455ccb123a5f899ad4f1103977e53d50f94b5499c7ea",
- "setup/Installer.php": "f73d369b5629244e8a38895d1fa35ad9ed725523914a0c6034ed3a31de095483",
+ "setup/Installer.php": "44d515370518a276696b6fed8e342bf5488720d486c27304c41ae152e7967a5a",
"setup/Localization.php": "e6de0faeaa493fc317907099df2ab58c76f7b643ac70ea233deaa5bcb86d79db",
"setup/PackageExtractor.php": "57cb5473905992333936d7984221e5188cd72756d6e48206a42d96e8ab59442d",
- "setup/assets/installer.css": "4b968e19d2b2b5ca1b4fe265f6ee78ff8812b4967751ad71501a7a20af43082e",
+ "setup/assets/installer.css": "79e43dc7123f37206429e8535d92c5069c53231402c67b63ea9481bbea60ab42",
"setup/assets/installer.js": "892a982b872390fea21455562353408da906114b973ccbfc3a3cce06465ab1e8",
- "setup/core-migrations.php": "520ba71a85b8035c52d340696e17203e3c89829b939b4e1129010b66e695a067",
+ "setup/core-migrations.php": "493713e0fc6ade5b3bf866a4bf11b2db1e9e64ab5c5859aaa169df5cd643b8f0",
"setup/index.php": "63bff35919aa2f4cc9ac817b92020d01d5e8a8d6ec387071e0d1ece1edaedc15",
- "setup/language/en.php": "cc62f651ff62f75a5c1cdc591d0284ab6e7271175061fa8a0e026ec16df06d7a",
+ "setup/language/en.php": "963dd284ee244e6d006dede8522c1118dc0909974dcd24d477fd06b7e1965fa1",
"setup/language/ru.php": "8522b13c4f147804538eaa857585296db811c736e1361e6b403daecea05f34f8",
"setup/recovery.php": "359ae9b79508a382bdde175824b18a5bdb0fd69ddb7776c7d18ac9eb826d127a",
- "setup/schema.sql": "c23be35e4eb2e2fd755a5a359c44e3ddf9b33614e2d966b5676aed9770975757",
+ "setup/schema.sql": "c4e134bf6ec81dad3a46dfa6d2f990e10104e188f54ab21cdbd0a9cb4e53e2af",
"storage/.htaccess": "ca761a92f2b6eb47feffcf200a8fd846e7fdd8abe065008d29beff0319ca3496",
- "system/App/Common/AdminAssets.php": "89364e67c30bde6df608eaa59c20584e7979995710500e421cc27a54e2d3a262",
+ "system/App/Common/AdminAssets.php": "2dbf63a3b5e69ca430391e17420ef9b1db7f6329f28fe510e8b9da5e7b20f68a",
"system/App/Common/AdminLocation.php": "d2729b0d627ddbdeb7f0c4b110664ed949f373946f80929c51688391c64352de",
- "system/App/Common/ApiTokenRepository.php": "94c01d985776ba8c8a3b7c533b1c17425e2af713fb24df01d822bd191a8acff5",
- "system/App/Common/AuditLog.php": "25ade9afb4ff843e341120ef3a204f6b72993cd9917e101237db8f47e245298b",
+ "system/App/Common/ApiTokenRepository.php": "2d750efef3e5f8624e3f307a6a8074886d983f324d74d6455eb3ed0061bb57a4",
+ "system/App/Common/AuditLog.php": "2961c0545d395e42d21119e1a8beafc0c3ffb3015ee67dd20edac4d1f302082e",
"system/App/Common/Auth.php": "886b33e077babf3b8d9fc4c14bb4535ee12aed8a34799e16e4f73ab657c6cbba",
- "system/App/Common/Auth/IdentityLinker.php": "c614215dc98de43a6dd31c7c4435cac3540acd886b0f450f6e0fdc1537276563",
+ "system/App/Common/Auth/IdentityLinker.php": "7da4d902405bb4ead2278e5bc3578cd0318422fe82318bec94257ed44d5ba98c",
"system/App/Common/Auth/PublicUserNames.php": "0345d1f452e0a9accaa9b98eea71f27a428692a5b4f2ad225a10ad8bcf777684",
- "system/App/Common/Auth/PublicUserProvider.php": "b24beb04d30756d8f7ed30ace4d0e5f70882e5a28989080b6c377baffd8a77d3",
+ "system/App/Common/Auth/PublicUserProvider.php": "5661296372762ad2b3a4297d4ce76f171c58ec4343ab585b71f58b856bdad96f",
"system/App/Common/Cache.php": "bb322246cbfa12cbcdd67c16a24c55715ad67b31d356b4f18091c0aa1d00eb9a",
"system/App/Common/CacheKey.php": "47400cd90d1880d061995bfea585b56210e18a870607d9b1cf7f0eeaccf15032",
"system/App/Common/CompiledModuleRegistry.php": "9ab055fa759f4041944bb94bfc2e28361986a09b742210981928bd6ee15f758e",
@@ -843,7 +889,7 @@
"system/App/Common/CoreUpdate/UpdateRepository.php": "adb226ce961f920ab97cf195e277c9e60a93ab846bde0e34492ba66b53caae16",
"system/App/Common/CsvLogWriter.php": "9deaa3e2bd85eec300c65e89e22549f48aa9e2850ad50b2ee8826f8d4fa53aa1",
"system/App/Common/DatabaseConfiguration.php": "3eec09e9a0d9302761ca6b9ebfcb47b01aaded1bb7ed5834b1d55cac806918d3",
- "system/App/Common/DatabaseSchema.php": "2958c57ae90c35824c76c17e4230ae3041a9be3d06b1a2916264ba5f8b5a834c",
+ "system/App/Common/DatabaseSchema.php": "a71320db73eace5f630f2106d402565702c88bebae8056aced6daf922a78ab88",
"system/App/Common/Db/Core/ConnectionManager.php": "dcd609e118916eb8ae5c0f2d5e38e18d3f9631395342de44f104dac3c47908c8",
"system/App/Common/Db/Core/ErrorHandler.php": "3da721f57d469fa6f339bb99cea8fd2a98c2bf11beccaf2d574005090a29dbd2",
"system/App/Common/Db/Core/MysqliDriver.php": "130b99106ef29ceb69939ea5a5dd820d2745333154b28daee7da21bac835466f",
@@ -866,7 +912,7 @@
"system/App/Common/Exceptions.php": "83b5dbbb5791789a9ba5fa69ac049037742e5d8622e40e125860405935f7e0e9",
"system/App/Common/FileCacheInvalidator.php": "24d34113c9909e3c56d74ea35ed61a1744c20f7fa7ecaef49761f2a28a774a18",
"system/App/Common/Flash.php": "8fcdafb920b374a6f346c10a42e3f846003fccba97f4bb48fa7028aae2a0091b",
- "system/App/Common/HookCatalog.php": "3cb5a62c901ec173097cea78935f4e00b05473ecdcd4e9be2ec0f4ee6ed07e28",
+ "system/App/Common/HookCatalog.php": "52e151f1b41856436d13cb6fd6610ffdec264225a0ff7bd29761ad405ece92a8",
"system/App/Common/IpBlocker.php": "5536f4802233588671fa5aaace1c017ae8f52590b64213d5d9681a608ff3c3b9",
"system/App/Common/Language.php": "587672d76ade8b5e1b30e98796e3656a833515db4d09b44717da443e0fa7ac1f",
"system/App/Common/Lifecycle.php": "4a59040f213232dfc9cc5c3a9bee491eab5916b65accb47c9972ecfb2573b68e",
@@ -877,30 +923,30 @@
"system/App/Common/LoginThrottle.php": "de6550ad689f5743dee92e5071bbff1a12a68ffaadcbe08e2e5fffccc51f98d3",
"system/App/Common/Logs.php": "d23e9a3ba1ef925b37b756d4dcbba47b42edaa0ad732804b4c99d1dd2c0d8ec5",
"system/App/Common/ModuleLifecyclePreflight.php": "66749f25d63dde05a06cffeb75a0a22c5ead68e51ef6ac71578384fecd8520c9",
- "system/App/Common/ModuleManager.php": "5f5ed1aa57e6d8884930ab4f5bf18077889eeacaa93ba66febbc7bbf9071defc",
- "system/App/Common/ModuleMigrator.php": "d9aea1aa006a1736d8314823053ecbb970cc7553f9af315b8b6fc092ffb07d83",
+ "system/App/Common/ModuleManager.php": "20952e53cab9b716ad2fb518021683df7f6918e17f38e8cc0ade1d2e9ed27776",
+ "system/App/Common/ModuleMigrator.php": "121d2c21fc491279b36cbf5159ff401ba47ab7f57d7f3d502cedda746283a364",
"system/App/Common/ModuleSecretStore.php": "6ef62294077db7f09b85ce26f3167b39cd980547d1f21bee213b59a9c7e2e6da",
"system/App/Common/ModuleSettings.php": "b3280ad547e59bfcefb4a338339a36905fab5db1548345b0f00e257d275a6294",
"system/App/Common/ModuleTagRegistry.php": "9e68b64303825a25ef4f71f31a7adedee9d2752e4d1d06162605da1dc510a6a5",
"system/App/Common/Navigation.php": "1a1b0a3552d941dc0fc59c334ab705e86a73ee96ee42871dc958012bec1d81f6",
"system/App/Common/NotFoundLog.php": "3b1cdeefd05ae250e90d1a05dd9e1bc0ba2cb0c2cbb89d504fe009151f20452a",
- "system/App/Common/OutboundHttpClient.php": "ef46209e2b093fc152000b050bad6d5d497489486e09abc78cceabc0176ed2d8",
- "system/App/Common/PackageModuleRuntime.php": "8afcbf0439fb3b69b6b9e098109eeb2fc1521d3435d5d2d654085d5bd2b2a470",
+ "system/App/Common/OutboundHttpClient.php": "5d1182b75cbb507a962fab64c11ca3922826bd9810dfbbf3903589edc686e9ca",
+ "system/App/Common/PackageModuleRuntime.php": "5ad492b9053bcea4d3bd4398b11fb2d976272e2036d379bdc24c19dd5fe7486f",
"system/App/Common/Permission.php": "8e2241bca829de0104741dc2518860cd15489866dc310437e07aeab2bf0f0355",
"system/App/Common/Proxy.php": "707564208ec8c24ac2ffb5bf41cba34f1b39059ce6bda31a69aaf4b116d864e2",
"system/App/Common/PublicAsset.php": "06f9ae414f7336206073e07597669991098114c396e80748c654c82b370a39c4",
- "system/App/Common/PublicAuthSettings.php": "521599642c667a00b610dabea8a4a8f2ef79af48fb6c907878882f8252297040",
+ "system/App/Common/PublicAuthSettings.php": "d73f2ee75141e055be0968d190ad11190a0b1275cdc0330670274b46b8206f07",
"system/App/Common/PublicBootstrap.php": "1208a8797bbe695d4607fa804f654402883bd47f926e0a39fa8766928c16e509",
"system/App/Common/PublicConfiguration.php": "1daf6c26de04277f8618be4e25453aa479de9f7dfc09ba770d405eb7f9454760",
"system/App/Common/PublicModuleRuntime.php": "5aafede8a2c0cf35023419a024566ca453676ea2dd9274a4d5ca99faa9273d15",
- "system/App/Common/PublicRouteRegistry.php": "dc5129b12f0969a32ed605c8bece174fed11b679ce4328d426099e3e38ccf688",
+ "system/App/Common/PublicRouteRegistry.php": "6898abe0018f2630807f9697306ecfa2d63482f38832ba556b15eea21fa14222",
"system/App/Common/PublicSysblockRegistry.php": "0b9112a40f98699db6d2ae802e346fee27ece77de8720b2dc30d8b5cf207ed81",
"system/App/Common/PublicTagRegistry.php": "787e3cf4014b501fcbf24dcdbe552a0247e07586826f5ae4584b86bb781b8d5d",
"system/App/Common/RateLimiter.php": "906313be7ff8e2570dfac8b7c261fa74d6e814e052c40ceeac1cd840c7cca3c8",
"system/App/Common/Ref.php": "e5e55093871022e86a7c8e8841e2e150922a7454500668dfb6f30fd50aa6044b",
"system/App/Common/ReferrerLog.php": "a2da447a548011c9ae6f8be2ef4aacac2a3d4c38b62a19d7ea5442589cb7febe",
"system/App/Common/Registry.php": "7b4ddbb3f96a7ad54a8d43e151cb595e598d3620b368d21721927ce9d54e1ea3",
- "system/App/Common/ResponseSecurityHeaders.php": "0c20afd7d42f0d878c53f6088dbe4c03b6fd26e57d82a040c775fef180b24a7e",
+ "system/App/Common/ResponseSecurityHeaders.php": "b159a7105d3c1fb3ffdefbbc5997741d7b0f83bfb26fabcac96f61b53969364f",
"system/App/Common/Router.php": "4b0dd3fd9f732177070ce3326b691a8decfbe698aa0d8cf687a18f6c02b73d91",
"system/App/Common/RuntimeConstantSchema.php": "08daf898ed9bfa12940adf8ddcd3e0dac811d28de8b798770afc42cbd20f1125",
"system/App/Common/RuntimeConstants.php": "decb1277dada75ee2b5c7d531f064db7ffecef5b3ad71d306111b2a45121d34f",
@@ -912,20 +958,23 @@
"system/App/Common/Settings.php": "d739b499a2d3c893f387cf3f5033815b863edf08414b1565063fb9d5e7d53f0e",
"system/App/Common/SettingsRegistry.php": "0a24cf4e55e48d05a8af09ac00bbcbcc1179385254af8fd914e754964a1362b8",
"system/App/Common/StoredPhpRuntime.php": "2bd4fa83c970cfa0b8002b11cf36bd0def7b37ea19c025f7c6b2f37f0de38286",
- "system/App/Common/SystemTables.php": "dc1830e972a32a87770bcfa3b4240524f49bb9f93df75d48e8ae05c0cc2004fa",
+ "system/App/Common/SystemTables.php": "85e95425b64d35bd8a3a5d4ddb14c1367ecbaa64ff6aaf4378055a1019895806",
"system/App/Common/Template.php": "5a0cacde5059fa9918321e6baa71a09a754a024055dedffae161486c51902f45",
"system/App/Common/TemporaryDirectory.php": "4aad5e498b0ef5483427c18ec6eaaf9db64ffd1348a0ee3f445da080987e4bd1",
"system/App/Common/TrafficAttribution.php": "cef0bce9cbd2ca3d8dd297cf1b969669ed1cb996cfa175841ecc6836ca7eb8e0",
"system/App/Common/Twig.php": "c6b62e0d1a9576082babe49d9d31e8f9f63016f819a418dd723839851a32c455",
"system/App/Common/Twig/TwigExtensions.php": "b9db3c89ea361b8b4ed754741d5692fc05831a7523a7f9832a9128c313a5b8e4",
- "system/App/Common/UploadPolicy.php": "a8ad388681a65a31981767c2d1c17f635f4c490a17803f35d3548a47758c596d",
+ "system/App/Common/UploadPolicy.php": "c6423e3300bab55fdf54bb59573111fc61febe0d10c3af9b143a88f7d0732e49",
"system/App/Common/WebMaintenance.php": "a91f9ee8371da6c6a020d9ce39e1f9f7b517b2644ea51ada65f1f487ff337e6b",
"system/App/Content/BasketTables.php": "1c4d1d9a5ff894c6ebb0edbcfd7bb6f791124e46dd8bed3b70fb0ef2c15069a0",
"system/App/Content/Blocks/ContentBlockRegistry.php": "53bc204e7dd1f9d61781049627162341a4d7e656783915161fa5c4eae4cfa228",
- "system/App/Content/CatalogMenuRepository.php": "1ef7e4fac76f1b1571eb85611002cbb18bfd61829471fb189024c651efed8355",
+ "system/App/Content/CatalogMenuRepository.php": "d2b384255fb9e67264f071064dafd645913a73ee640e559d600b1671455443eb",
"system/App/Content/CatalogTables.php": "5abd93b17142ea5c57a0726c8c6e3dcaf896790cf4ba31ea69be19a828ac9517",
"system/App/Content/ContactsTables.php": "d0042c761b854903b483e1b3a30d6014c596eef1a03c40cdbedae4fecb3c25d8",
+ "system/App/Content/ContentPlacementMap.php": "b4328ef770c98ab8f93d395d2d13ca5ff54d94a2679977ed2615e4d33c50d55d",
"system/App/Content/ContentTables.php": "a63105d8f3859921a9c1d17a4b8c69d3950ff0294de144814bd2b4768d236fde",
+ "system/App/Content/ContentTagDependencies.php": "e96882109203f4db60559cc324100ce5596c8f8a65571ab53b8b86cdd3d89fa9",
+ "system/App/Content/Directories/DirectoryRepository.php": "47d4f7a91841ff437c4448aa71f6a2b38c621dcf5a1204bd2ff4948f3ad671c1",
"system/App/Content/DocumentTerms.php": "353d0a36e0cef77d4016f9edf9c2a2a3a27db953a3eb4cb91d7ec49af18b0609",
"system/App/Content/Documents/ContentCacheInvalidator.php": "d2bfd72d4df1a05e281b88543fabb8356ffc9799d37286c3767c6a0173ea91b1",
"system/App/Content/Documents/DocumentAliasRegistry.php": "aa87cb8b65990f480f5b4f5dea83948dec9758fb4069b43eeba4be7f7752be51",
@@ -939,12 +988,14 @@
"system/App/Content/Documents/DocumentMediaFinalization.php": "5ea218f79f835a4fc25da0199aab87137e620b60c31d8e2f587a1b911b3af6b1",
"system/App/Content/Documents/DocumentMediaFinalizer.php": "785487028e221726c6bc15662f83ed985701a8e5d21e9fc9605e816ef4c835b7",
"system/App/Content/Documents/DocumentMediaPath.php": "23b40c98d3cf9a73df3b7f7083b58b60dd80b8bda71e3e3a6354342cc72322a8",
- "system/App/Content/Documents/DocumentMutationService.php": "44e83fad0d2cd04df2d1a9338891984f52b06f0b886caef59ef2f6d1281c6784",
+ "system/App/Content/Documents/DocumentMutationService.php": "52e8f539cb62a3d787bdbae9e2df5d8d21bf75a39fa46c1d25545884b2b022ae",
+ "system/App/Content/Documents/DocumentPickerRepository.php": "0e8efb5b42aadd74725213b8302b53b5147d7e0eb2e83d301a5f073bb0c13142",
"system/App/Content/Documents/DocumentRevisionPayload.php": "c7f83350de98b6d98531508eafecc4a2b5a0551c7f2ba77a9e5b954fd1287ea0",
- "system/App/Content/Documents/DocumentRubricCodeRunner.php": "093fd372fc01f0ff1640379715c058ef52db2451e66b65bed84cc2d51b49ec2f",
- "system/App/Content/Documents/DocumentSaveEvent.php": "8a2798041cd72be693278cf91c04358a7f486d1ca8035b17651455f199312492",
- "system/App/Content/Documents/DocumentSaveEvents.php": "52fe587fc4419b5ec86144f522b3028682b384a52dafb9690e0928f8e68acdb8",
+ "system/App/Content/Documents/DocumentRubricCodeRunner.php": "a9c04196b76ab91c7b1f8b53afbd45fc6727805c953fb5fe5ff36b69846d389f",
+ "system/App/Content/Documents/DocumentSaveEvent.php": "ad30286d10b734e090732f84f0e6a77bff050acf653949140049418dda8663bf",
+ "system/App/Content/Documents/DocumentSaveEvents.php": "79a9fa78819938f179bc24121992e7d44306c51612734d9ef10839b764a5d394",
"system/App/Content/Documents/DocumentSaveRejected.php": "4ebdc1c5aa622f6389b107320a7786b8acde89438f6c56a660317deb623cec5f",
+ "system/App/Content/Documents/DocumentSearch.php": "ba8d1c7a28878e3b7e2c96aff07c59c2ca9b5b5eabb4eda99de6362b4274d54e",
"system/App/Content/Documents/DocumentSnapshotBuilder.php": "1d9437433d721198fbd18c6cb141657da93c569fabba46bc59466981b68b5ee3",
"system/App/Content/Documents/DocumentSnapshotRepository.php": "54faed521c064f84cc4017e65ea0dba8b9bb34571240a90c083eb076c9a11b1a",
"system/App/Content/Documents/DocumentSnapshotStore.php": "e61a7873204ba76b50d2ae7f087a59442cb93fa10ad5c3f0ac0087d941dd2075",
@@ -952,10 +1003,11 @@
"system/App/Content/Documents/RubricSchemaBuilder.php": "a391211bbdbde83a6b8f4ce2ccf98bad2ad2f5d39f35edb5e262782729dd86f2",
"system/App/Content/ExtensionTables.php": "7e59c929815afc570dbd6e0af754339ee94a1cb32b928edbc6d50cf34bcbe085",
"system/App/Content/Fields/AbstractFieldType.php": "a6be3a44263f07cddcdbeee600e48f224cbd948948740cd7cdda34c2d7fa394d",
+ "system/App/Content/Fields/ComputedFieldEvaluator.php": "8711b89d8d5c99ad847e11fa5c1d80e890cfc0cb35264d170b1fc3893f2df288",
"system/App/Content/Fields/DocumentMediaFieldTrait.php": "4f5845f44cae6bfa3333cf39ba56edeae580359dc5edbc89fc64cefa7912eb34",
"system/App/Content/Fields/DocumentMediaFieldType.php": "f2a461a3780354159a915f4f107a479c7326f255357ee82eec25d3bfc658b4d1",
"system/App/Content/Fields/DocumentRelationFieldTrait.php": "eb397fd4aabeea93cf4d280f7c680f10d92a17596bafaea8c3eddc4bedb17c78",
- "system/App/Content/Fields/DocumentRelationIndex.php": "40551bf019428d1f6f81f07f3b8f45d0ebdc349387303e4d9085c413d3453444",
+ "system/App/Content/Fields/DocumentRelationIndex.php": "981b582526d7dae372606c1459023027165e7cb47f5bac14e4735184c652568c",
"system/App/Content/Fields/DocumentRelationValue.php": "44abfd5820a0e58f7d7bdfe3cc460bb0db3ff33433bb94aa50dc3d4ef9b9152c",
"system/App/Content/Fields/FieldConditionEvaluator.php": "93ef44322ee4da475a2d7b7c0964d13578a67ededb3da8a5d7e5ebcd05aecf66",
"system/App/Content/Fields/FieldContext.php": "97e4d4171bba1ce5c5185068c33eaeb91fdfab670b1ce3fc9f56e10f5cca1d0b",
@@ -963,7 +1015,7 @@
"system/App/Content/Fields/FieldLifecycle.php": "ee33d7f6e821f71d39bba73a73782021a061edbdd0be7b34164f8b94ad505904",
"system/App/Content/Fields/FieldRegistry.php": "c993388ddd8ec9cd8496d3e812da83a271ad30271a8ee87b79b2f94df84aba9a",
"system/App/Content/Fields/FieldSettings.php": "eb849a290e61d7149f69475d1319b06e5b19cd56f1cafdec78f95f37a20e475b",
- "system/App/Content/Fields/FieldSettingsForm.php": "c3988a6fd9b51ae3a2efbbe77fdbc9539090c8682c97329d4d40c64a9c1cd899",
+ "system/App/Content/Fields/FieldSettingsForm.php": "e436743e17fab7aaf14dab6ce034fa2ece2f5d165a4aa04450d034606c8e2a73",
"system/App/Content/Fields/FieldType.php": "47979aaa49b773e2a00288ffccb746218b58ece8fa6d178a90d0cc9c853055d9",
"system/App/Content/Fields/FieldValidationException.php": "1a1d6d5278d6f8ff697e5afe75e0a6f59db2b12121c93b786fd87f8b81287717",
"system/App/Content/Fields/FieldValidator.php": "5408849670818b379a66d5db9b7aa0d94ebbd8438d7788124f62cd221dae5561",
@@ -979,9 +1031,10 @@
"system/App/Content/Fields/Types/Catalog.php": "2126e5a6bcb097f96c5851a8b84a5fbbf04c0c83de200491b9a68d51caf5b597",
"system/App/Content/Fields/Types/Checkbox.php": "ad762af437e80054e0cdc1e5e16712899590c2806d6557b1dd730088392a0e7c",
"system/App/Content/Fields/Types/CheckboxMulti.php": "81fe85ee7d0d55c26d9bd260a0e75aaed4ac516d6da77cf3071bbe309b1c1cc5",
- "system/App/Content/Fields/Types/ChoiceValue.php": "fda95e47a525e6e6e49b6a74fc23876b657be091b0ecfe3bfbaface6af9cb662",
+ "system/App/Content/Fields/Types/ChoiceValue.php": "e3dbb710847d2317cd08b95b411cace18c3b01312971991b3073f6f61ebf599f",
"system/App/Content/Fields/Types/Code.php": "b517c1ad6bdde09ddc80ad83065ad30575d8a9fee804bfc54b3324af9c334d98",
"system/App/Content/Fields/Types/ColorValue.php": "8a68a4fdf628dc30922357bb0918235f373e76034d137afa270004edcd947740",
+ "system/App/Content/Fields/Types/Computed.php": "82ff8ff37fdb9802a6626edb7abfe97e0867901157b23d05fb75a84710aa851c",
"system/App/Content/Fields/Types/ContactValue.php": "b6f8533a8de393b4c760ea69423b8bd1c1fa0717bc5a608cca7710f58c751f11",
"system/App/Content/Fields/Types/Content.php": "e7e41bca3dc574e5735446593ba875d59ec1baf4769cb130ecdcecefda65709d",
"system/App/Content/Fields/Types/Date.php": "4952e8ff31abdc8d133e6f812e075847a49790c9fdd1865357cbb4080b0bb8f0",
@@ -994,23 +1047,23 @@
"system/App/Content/Fields/Types/DocFromRubMulti.php": "5ebbc0abf7c5f0306d46c01e2173f3750c418377883605a75d4deea3be0f9218",
"system/App/Content/Fields/Types/DocFromRubSearch.php": "0b91d898710c1829de2a404e4e259c0cf926dc7e987be9eaf2e535f9ab86cfb9",
"system/App/Content/Fields/Types/Download.php": "8906a382c05547afd112778b02f0fd21a1061bcde4e3b72d7e3682acd13fa88f",
- "system/App/Content/Fields/Types/DropDown.php": "e8252d211d9e30478352a7eea14f758f054f3c03b12b9874cff2390315e1cfa5",
- "system/App/Content/Fields/Types/DropDownKey.php": "b50c064a38b10da7065851ea9b8f0fecd74d6a438dd7a64ac4abfba89ae71282",
+ "system/App/Content/Fields/Types/DropDown.php": "8f2b41db0a14a14d24b48b0d83952cf19ce47032f90be442ddc18594c85ee4bb",
+ "system/App/Content/Fields/Types/DropDownKey.php": "ea2f1dde117704a44034ecb98d7d481d8f02c7bc130782dd9e9320d429779c61",
"system/App/Content/Fields/Types/ImageMega.php": "c6e17503fabfeab926ae695b4e2a7c5dd99f60dd5bf0ec84b04bec6b96c6292a",
- "system/App/Content/Fields/Types/ImageMulti.php": "cc1d9a5d8367e111a3b4da9eb5c7112a78fa438be505e5f32ac5ad579e2c7a2f",
- "system/App/Content/Fields/Types/ImageSingle.php": "a4592703b10f25e1a97e82d1cbf79515233a8ce93d35598f9c77f37db70f4c21",
+ "system/App/Content/Fields/Types/ImageMulti.php": "ee1312c1f59a35af4ae37f9444f4875752ed09d83692006c2f17858a33b74c48",
+ "system/App/Content/Fields/Types/ImageSingle.php": "54c1253fcdf6c3726b2f6d4881d0bc7844fe3279e50f1d796d2bea14ed93dba1",
"system/App/Content/Fields/Types/Link.php": "6c3b7031d8a1b78371c041b142a90a373217afce12e637d29cc0a6b4ed126d7b",
- "system/App/Content/Fields/Types/MultiCheckbox.php": "26f4f9d1bbd7cbc24bcfb827565e9415e27f31fe1a2bc644e1f42760c7d1f213",
+ "system/App/Content/Fields/Types/MultiCheckbox.php": "040dcf7824d15a5bb9cd032ce2de171a91c8f4379e3482a85a59d6c7b34fc2dd",
"system/App/Content/Fields/Types/MultiLine.php": "9cdba0fb14c1053a2c88c667f436eed2da243836d49ca6c3240612f32bdfab52",
"system/App/Content/Fields/Types/MultiLineSimple.php": "0286b13ad1a2e83b5fdc8b11f05bcb99b80b38a018650c25e58141785fda0fda",
"system/App/Content/Fields/Types/MultiLineSlim.php": "4c39e5810a9e2df82a7e03af7eab36b07c20c863fc31d3aabc2e172103b41d84",
"system/App/Content/Fields/Types/MultiLinks.php": "a4d1ae15f0c23934a5185fe7407605079873f7a018c6ddfae0998f2d1c3312f6",
"system/App/Content/Fields/Types/MultiList.php": "1ae0262a38c7b1b3ad67688bed05beb620745969b36fa5ffc6b7b052f16a40f8",
"system/App/Content/Fields/Types/MultiListSingle.php": "45b86fb20ef18d9ffc6d1f3281b7ca8e6593269e778383cab8cdda0cfed2b9a4",
- "system/App/Content/Fields/Types/MultiListTriple.php": "431c96998d0d9316a63c9521d5c2b306f06501576632f4deb5c04eb3c25e0891",
- "system/App/Content/Fields/Types/MultiSelect.php": "cfd50ea68a31fbbfb069e4830077b0183ddc7686efdcd97f7509e9108564ec18",
+ "system/App/Content/Fields/Types/MultiListTriple.php": "8118774650d49ed98cf00329bf3d53b20a05ed7d0473909eda4244943be550f1",
+ "system/App/Content/Fields/Types/MultiSelect.php": "b38df4b46b529b6c60c9aaf001faaefef5ac7599cf68ec0323a3186609e37d4b",
"system/App/Content/Fields/Types/NumberValue.php": "8fe7bc2aee7df6031598384627c8c38b8b4a5b04d796c25ee8aab768805d34ad",
- "system/App/Content/Fields/Types/OptionFieldSupport.php": "73015455939f82179cc0b861d3b94faf6fac27bcec303f958d252c31f8744871",
+ "system/App/Content/Fields/Types/OptionFieldSupport.php": "ad45ad626daceb7a83f8b0d80faa90dce4727d7d344a0da119e3e6b27fbef54f",
"system/App/Content/Fields/Types/PackagesValue.php": "b83d3ac394c42204ab89d4edcc5bba85e95c8850d07839f53273e958db346c11",
"system/App/Content/Fields/Types/PeriodValue.php": "afe067af7705b027652e2210ae784977a4fa00344667b672c3314d463d238197",
"system/App/Content/Fields/Types/RangeValue.php": "396b6dcc4f4b4a1875c033c31dbf7b1e4ccd120a79bd86cbde813d7ec57bfa40",
@@ -1023,32 +1076,46 @@
"system/App/Content/Fields/Types/Teasers.php": "cc49fbf43feaa1e875df65a8b7c3dc47c044ff52ddfdaa6bd3ece8adfdf9891c",
"system/App/Content/Fields/Types/TextToImage.php": "5a272c24db87738b6d0fb81f73f9af6cb74efa1baa03d74b3d6c8783c0fd5724",
"system/App/Content/Fields/Types/Youtube.php": "4724937ec212360e8da7d28f4e1a35e2308325de77b867545236a6645d9278b7",
+ "system/App/Content/Introspection/ContentDiagnosis.php": "dd10cd446ed49202bab64e586cd09d7b385d9875b09fc07d20c2f779775ba33e",
+ "system/App/Content/Introspection/ContractRegistry.php": "b001e60d8dd085ba133301ad33285a130daf7ed69519413e314d21e01142c94a",
+ "system/App/Content/Introspection/IntrospectionSanitizer.php": "99fddd82e5f107fe1b8dd1b41c174a9e2dc36e72bf28305041883338ad9db8e5",
+ "system/App/Content/Introspection/IntrospectionService.php": "5a5de2ab64f6e15c1d16c6d3883783e18c3439383d14ef626f4b11542658e1ec",
+ "system/App/Content/Introspection/PageContextInspector.php": "aa474fe89a8da647d5935ca2798269e642c99efc7fa7152fe3e288b24da561d8",
"system/App/Content/Packages/ContentPackageWriter.php": "dea33706a29e11d2a04ca6904bad49f8ff6c062b2cc1eeef1bf9ba18f49adb00",
"system/App/Content/Packages/PackageDependencyContext.php": "8a1f8ca6bbb7add9d8af11d28cdf7137c9a9eaf25b528b0259cf9bd0be00c8b7",
"system/App/Content/Packages/PackageModuleProvisioner.php": "28a922ac5471d86ea0c467de11164c97fecd0ee432814e79927bb875995629cc",
"system/App/Content/PaymentPrograms.php": "d233564780b9ab9655f8c41c490aad207c95731f36e6967fb52ac54b9c89e52e",
- "system/App/Content/ProductShipping.php": "4a33a877983a8509ce35fdd88ed95db43591d3b35d5e4f5d9a5b67c566434c9b",
+ "system/App/Content/Presentation/DocumentPresentationBridge.php": "be90d44accf2f75435a62e8be5e79184e095708a3601784daaf87aa4e05ba501",
+ "system/App/Content/Presentation/PresentationAssignmentRepository.php": "a24059e979dfacf7d06f72a46a0322f50b921d0361ed8a426a64b393ca387cca",
+ "system/App/Content/Presentation/PresentationDataRegistry.php": "7159542177443ab7e84ce85e52764bfd7df4448320059175dcbb151ef40eb38c",
+ "system/App/Content/Presentation/PresentationPreview.php": "57baa912b0c121efaee7f8a524ecc44462fc25ac3170e54e747a45210e8dbd56",
+ "system/App/Content/Presentation/PresentationRenderer.php": "989a0e90da4e65790c3152c8b584b039f4f1f101d5aa2c288080e0548a68f357",
+ "system/App/Content/Presentation/PresentationRepository.php": "bc5fb81cb6068ac54551463e563637ad9d75fc5a571d27e8642ed19e7a43b118",
+ "system/App/Content/Presentation/PresentationRevisionRepository.php": "061af7ebd841a44a24be91e6ae3d3b497347bb5333819c74712348b81c40c2df",
+ "system/App/Content/Presentation/PresentationSyntax.php": "aa15536fc06559d34b9aa6b248136e8314f75997b467083dcfff4f1445fe44f9",
+ "system/App/Content/ProductShipping.php": "8a92b23991b502a97ee3d718add3e87e05382ff80aa83e3c657f54400c3745bc",
"system/App/Content/PublicShellTables.php": "f89cd224436da9d1122c988a3a0ac19e5bef85351c51649506fe16a05c514cd9",
- "system/App/Content/PublicUserTables.php": "56745969410ab9e8e4272103657f805e346336db34166461de7f2113a6a52ac2",
+ "system/App/Content/PublicUserTables.php": "f44a184075c9f978564f4b413cb3e89cda5f17528459d52258f458cef87941ab",
"system/App/Content/Requests/NativeRequestExecutor.php": "988a1635bb4602b55edb89a1fb8dd26ee0537c074c77fc86b00ecbd8800517ba",
"system/App/Content/Requests/NativeRequestPlanCompiler.php": "9e583cac4c41ced0c105d27c76786857ee50d15bb86d0049e6562924eab78779",
"system/App/Content/Requests/RequestConditionCompiler.php": "fa076e03e00907a7c47c6072bfb8f6a8406378c723e331fceedfaad59068d58e",
"system/App/Content/Requests/RequestConditionValue.php": "5a6122e53c9c4dd8d60691248c27d36057a77170c0c8bf0d8dc6a6236600f7ca",
"system/App/Content/Requests/RequestExecutorComparator.php": "11ad4524e3c86e270b343d5b52960a5b98a9d602d78bb199e92f69f4c64fb47b",
- "system/App/Content/Requests/RequestMatchExplainer.php": "884f01b19bf0a8cbee7694a799088b7d9d63297eb1980e79c797661f069ccff0",
+ "system/App/Content/Requests/RequestMatchExplainer.php": "f1d977989d388e61e18299ab28ccb688263d56b5a8a108343ef54eeb4f994b2b",
"system/App/Content/Requests/RequestOrderDiagnostics.php": "cf11ec143b145457dd004fc1c2e65c8215fc39c42519d90f291987e62596490f",
"system/App/Content/Requests/RequestRendererRegistry.php": "11b8496d0135fc54e86b3442bf1b2871a0e6010dea54ac2d3ef05f44cf1bf9ae",
"system/App/Content/Requests/RequestResultContract.php": "4dfcd11227117722651f9f7e7c54cb7458754ccaa64b31b6f8deb161e7704a4c",
"system/App/Content/Requests/RequestRuntimeValue.php": "9f0b9eeec1bf32cee8c13e1298e6b64c5a59f51c05d86b798523125a91ea8274",
"system/App/Content/Requests/RequestTieBreakerAdvisor.php": "bd547d4dd74c45246e3a49bfef3320964d723c520fce3339ea5884b89aabd08f",
"system/App/Content/Requests/RequestViewPreview.php": "ab80344f456d50aef43716572179b411a60af47523a71bbd45d317cc5967e899",
+ "system/App/Content/Revisions/JsonRevisionStore.php": "46b07200bd19bf0fe1d177e47ad84ed4de531b6271ebbccc80a9ff40c44b538f",
"system/App/Content/Rubrics/FieldSetMerge.php": "15ae21c53a11a3c40f59abe3b117c640506def0355964216ad7350f8cf8e66d5",
"system/App/Content/Rubrics/FieldSetRegistry.php": "a9df395474b9a28cdd1dd5dba40fe49e485835218afe472c414b9696094b9a70",
"system/App/Content/Rubrics/RubricFieldMutationService.php": "3fec278925d14f7ec2efed63ba51cd91def333220ca4e18f3a42a202a8806674",
"system/App/Content/Rubrics/RubricMutationService.php": "e3190ef1ddd94bdaf077148ce84c5758b2a32683369730a2b847dbbd0d72a74c",
"system/App/Content/Templates/TemplateMutationService.php": "8c7e52202454e35eb0c8176325cc23ba760ac40ff0430cbba5e1446c83cf7a87",
"system/App/Frontend/Auth/AuthService.php": "58a551f0c372194ffc2cca3e12f1e9a8ab743d07682f6b2c086ec1f7bc77ad54",
- "system/App/Frontend/Auth/Controller.php": "e6e44f8db67f822edda968f9503790ec6c80142c0e3d0fefe61a5f3eab186234",
+ "system/App/Frontend/Auth/Controller.php": "968a682faaad88e76f127b7381a183ee393ba367a51e40cd17395bd69d4fb4e2",
"system/App/Frontend/Auth/Feature.php": "60ff394cbf95ea3ca3f35476a6eb2bc4016fe472be85cd11ebac117635a54440",
"system/App/Frontend/Auth/FormTemplateRepository.php": "97882ee4b46e0c4e31493a44831564409b5a64c096363aaf812f181d13abae64",
"system/App/Frontend/Auth/OAuth/HttpClient.php": "b09acf92b312bd05ca78500fccaac3b77d36a1046d64d3615a00d60af4e3594f",
@@ -1071,7 +1138,7 @@
"system/App/Frontend/Auth/Tags.php": "92fc1c466e720be36a0adce5a735e47c94bf88d9a3e70ba9d62196ef63e639f5",
"system/App/Frontend/Auth/TokenRepository.php": "5e09b154d4747bfb3d2795a9b449ae4aefffe47508edef92d1a7b392c0371207",
"system/App/Frontend/Auth/UserRepository.php": "21bfe56cb9f2f74ab9c58947cac9e8132159afddc0900d7d7136e919c3482bac",
- "system/App/Frontend/Auth/assets/auth.css": "29724588981e3127908cb70b33fbc9711894e1e716d9618aa14309b172d65070",
+ "system/App/Frontend/Auth/assets/auth.css": "a16949511a17462d9157945f2ceccb445254b34243e9e368796dd3137aac49c8",
"system/App/Frontend/Auth/assets/auth.js": "840c7322fc21f9a8dd61af8cd16e0ceed36d6beaeac45d948bedbcb87b6012c4",
"system/App/Frontend/Auth/view/login.twig": "bbe2c12ce95f16cb2b2a07f945c8b8798e9386e9e3cbf16ddf5d0a3ca3944016",
"system/App/Frontend/Auth/view/message.twig": "4e9a660af7d0deae9424a07550816c401902e75279414b11657e0eddf573deea",
@@ -1086,14 +1153,14 @@
"system/App/Frontend/Auth/view/remember.twig": "fb170509256e4ecf13ebe5f64cae8cbd6ad8ef143abe645f4bc1f269e0d8a0fe",
"system/App/Frontend/Auth/view/reset.twig": "5dff5378aa0e2203fd67fe9f1dd61586ee51e3d3f6138011b9d2b5dec4da31aa",
"system/App/Frontend/BlockRenderer.php": "1d23d7ff85ba1052f896e204d1885f99ec2c0f6382e01d5d1903d69fe661305b",
- "system/App/Frontend/Bootstrap.php": "f67e5d9aa1dbbf228085b67ba4258169695ccc349f4c4a38ed9e1848e7e666dd",
+ "system/App/Frontend/Bootstrap.php": "7cdbfc2855a819b80e742eaa15dbbbf9d1f29ef47fda18860c82ea4541fdb8a4",
"system/App/Frontend/BreadcrumbRenderer.php": "6db4cbc31bc4e887a8ab4222c0359008dcc8d08bca969e9e3c3a098db12794b4",
"system/App/Frontend/Captcha/Controller.php": "eb398d3bdc711a0543dd293b00f250e05bda477fa4577e76b33fafc23f7d2eb2",
"system/App/Frontend/Captcha/Feature.php": "8e31714dc3101a3da8bac23989cba32ee9776a9f1fa5ec888a50cc65c8494972",
"system/App/Frontend/ConfiguredPaginationRenderer.php": "df4d28a386e4180fd0dbddbc30f08ca8c6345e2f3b0d196482fa9d2a5d4f4d32",
- "system/App/Frontend/ContentCollectionTags.php": "3efbbc93bf1244d0cf198615cdced96a0a3f94c442b926d4fb1b578b62b7a951",
+ "system/App/Frontend/ContentCollectionTags.php": "337085d73f0ad609b5a37a4fa72537459f4ae2e5f503e39a8750be95127d89b5",
"system/App/Frontend/Debug/PublicDebugCollector.php": "6755ad710e9d045b2bfa74745354f717c101236b5d64ad5530fd779468969a00",
- "system/App/Frontend/Debug/PublicDebugToolbar.php": "4df82a613b12bde3fe75c07418d2be87bf1b8ce0888cac072de5f3f0a9149710",
+ "system/App/Frontend/Debug/PublicDebugToolbar.php": "89a0fd4a1cab3ac8e3576cd8116332edbaaf6e09d3e05b0daa5798cd8f59c6ff",
"system/App/Frontend/Debug/PublicLayoutInspector.php": "8a7f5ea4cff9834ad13cac7e0d2380d878978e406e71860e1ad9ca183e427dab",
"system/App/Frontend/DocumentAccessPolicy.php": "13102e35dada9eb0e174189b976605257b48a32756e801d1ec5c358d0c0912b8",
"system/App/Frontend/DocumentApi/Controller.php": "7c5ffecf7e1515710931d7c31b5b2c5f555f8d35778a1fc202a8136b07c2490c",
@@ -1160,8 +1227,8 @@
"system/App/Frontend/RequestItemContext.php": "28f70292d7feeb77b88246d1d3299ff54447afc1aa6c525211835a4cfd51ebb4",
"system/App/Frontend/RequestItemRenderer.php": "a941b0ec8eb558792499fb0b9ea5b2ff049a59ddb0b61c3f364aa62fe959fd52",
"system/App/Frontend/RequestListReadModel.php": "7eedb83e42382cada17e60484108702a52f8a890b59d93cd921e194025af6064",
- "system/App/Frontend/RequestListRenderer.php": "03baf9b1df3e0488854ff2e0a2c1eff09bec0610244ac3385b009290d5c90a1d",
- "system/App/Frontend/RequestRenderer.php": "a064f09d308adda286400177a39b4d14a4f115f0bf4b1c31e559277ddd84286d",
+ "system/App/Frontend/RequestListRenderer.php": "b042f3961c9609a24eaf2b012dd62840ad183d938bf6312b32731c236ea07ef1",
+ "system/App/Frontend/RequestRenderer.php": "99145390cbfe803388786dcf6de60cb4f384224955681e7f855e4b7ef19355b1",
"system/App/Frontend/RequestRepository.php": "f0f97117e26da1dd93af7994b7d70ed99c7fd3dd1cd20a88cf799fe1a2b30f49",
"system/App/Frontend/RequestSort.php": "8585e125c76a1494aa6c481c9de1ba6849491099927be7cabf75604d37b4403e",
"system/App/Frontend/RubricPermissionResolver.php": "1223d6c2839952c98475bf8d802e4c1c9f058be3b2cadabe03fe7bb8cf3973af",
@@ -1183,7 +1250,7 @@
"system/App/Helpers/Date.php": "2ed2f352f52316484be9f915c73f90357d89aaf6945a398dcea1bed136c2c454",
"system/App/Helpers/Debug.php": "d495a96e844ed9109ec8eb71227e5f02155f61b08b719793d8be16a543f3c106",
"system/App/Helpers/Dir.php": "0791ed320ab894e2c768d16768c227e9c68bbc627723eae289f587551037fb66",
- "system/App/Helpers/File.php": "26299102f0ac69eb7c47ea4ecf37f9be29487cc66bcb0347deae1eebd6216043",
+ "system/App/Helpers/File.php": "4a43dacb8964e023a4361f2a3057da33a2b044628a4af3c7bd7e36171c024291",
"system/App/Helpers/Hooks.php": "266e65cd317245297652756f2cbfb0ae4af44f7f1249d9009d1c955594994803",
"system/App/Helpers/Html.php": "4b4b6387bf40ef4f91fef02ef115ee4b7230694eed93633ec5caa6afb195e013",
"system/App/Helpers/Ip.php": "e982ea7edd23850100b31bd4a82fac97bd9dc4615e13a3e559da266d8299b6ba",
@@ -1195,7 +1262,7 @@
"system/App/Helpers/Request.php": "29cc2cf20e1e4acd6e2f23c3de9b9606aec3f44d0b3577613b4fde376f7ae858",
"system/App/Helpers/Response.php": "beea1596703526b7a2424459e392487e8bc8a9ce216f8d70bd2b09564709e0b9",
"system/App/Helpers/RussianStemmer.php": "60a896c8254290ae554273132c103c1c209a14944f52cfd631e7a3ae13145529",
- "system/App/Helpers/SearchText.php": "f9fed5c018bf3337dcbcda97ef0b7219f11f13993678e61eb50858403e30dead",
+ "system/App/Helpers/SearchText.php": "1747521adb640161c6f4df648134c8349769e3cd7ddbc726118af81f1a4dcbf0",
"system/App/Helpers/Secure.php": "99a5a170cd574e5b6a6c3b433c5b8bac92407fba62088c93e893e215f18162a3",
"system/App/Helpers/SoftDelete.php": "9b85f227bbbc12e4bddd9bcf211b477044041b87bf94d1a422f80d46a10c3ccd",
"system/App/Helpers/Str.php": "7eddedb821b476f5284573171899a97ab653b23fc2777cbef780a802a1e9aac2",
@@ -1203,12 +1270,12 @@
"system/App/Helpers/Url.php": "418ed3e2682f9097b62d495769864843acc2a9650a90a98d24542acdfdb0946f",
"system/App/Helpers/Valid.php": "d5ebfabf53755ae3e66acade5dcaebd9ca0f5f151f988e1fa0a51d3dba9a0983",
"system/App/Helpers/Zip.php": "cf34358f6908b026e745bb94e5355b4376d46d31d3dfe78cabbf118c04f745a8",
- "system/bootstrap.php": "b65f00c1b32ee9d9ed8df7f7750108216fa1b3a347242822be9838891ccf629c",
+ "system/bootstrap.php": "2c72cc44f36c9250612b30f8f73efd510b47a5497a71b8a18658627e82e9bb59",
"system/distribution.json": "c498272f17ce6f0fbd79401fb264089d6380799a0a102c7f6e5c2fa2149439f7",
"system/language/ru/lang.xml": "7d46cc9822f064a1af4d6528ccfe2dd4a9cc318912e327fd807befd2ca3dfd11",
"system/preload.php": "66bfb1aaf36ba85945b729b09a37e96ead0be1cc2749ebd9abd4b9da4f25ba0e",
"system/public.php": "3f72b5bf44bc0ba5ba6fb6b3adc2e2f9d5e9902d9cdfa3d7b748f4aebd179b87",
- "system/release.php": "3f39c44d6c95e376a74272bcd4d91dc3669ab643279f67609de2de6ac93a309d",
+ "system/release.php": "c91d409134695fbf4d0016568b4ca5db774cec3b924364b604305d49178eb081",
"system/vendor/Captcha/fonts/palatino_linotype_bold.png": "38b6a1d4a4bd1d0c056205119c5e24176913ac0d898371f53a0339952a319365",
"system/vendor/Captcha/fonts/perpetua_bold.png": "55dbbe97dae128dea47b5e8d640414b3d813119c854c688037a20a581ec8fdc4",
"system/vendor/Captcha/fonts/times_bold.png": "d3d32ce1f418e3c3c3b1afe3b6acabadb36207bc12368f5e86420cf1a529ff8f",
@@ -1803,7 +1870,9 @@
"README.md": "core",
"adminx/.htaccess": "core",
"adminx/Support/AdminLocale.php": "core",
+ "adminx/Support/BulkActionExecutor.php": "core",
"adminx/Support/CodeEditor.php": "core",
+ "adminx/Support/GlobalSearchRegistry.php": "core",
"adminx/Support/InterfaceSettings.php": "core",
"adminx/Support/ModuleExtensions.php": "core",
"adminx/Support/Notifications.php": "core",
@@ -1829,10 +1898,14 @@
"adminx/assets/fonts/OpenSans-SemiBold.woff2": "core",
"adminx/assets/img/logo.svg": "core",
"adminx/assets/js/adminx.js": "core",
+ "adminx/assets/js/bulk-actions.js": "core",
+ "adminx/assets/js/command-palette.js": "core",
"adminx/assets/js/editor-codemirror.js": "core",
"adminx/assets/js/editor-tiptap.js": "core",
"adminx/assets/js/media-picker.js": "core",
"adminx/assets/js/saved-views.js": "core",
+ "adminx/assets/src/bulk-actions.js": "core",
+ "adminx/assets/src/command-palette.js": "core",
"adminx/assets/src/editor-tiptap.js": "core",
"adminx/assets/vendor/codemirror/addon/dialog/dialog.css": "core",
"adminx/assets/vendor/codemirror/addon/dialog/dialog.js": "core",
@@ -2041,6 +2114,13 @@
"adminx/modules/Database/migrations/003_document_fields_text_composite_index.php": "core",
"adminx/modules/Database/module.php": "core",
"adminx/modules/Database/view/index.twig": "core",
+ "adminx/modules/Directories/Controller.php": "core",
+ "adminx/modules/Directories/Model.php": "core",
+ "adminx/modules/Directories/assets/directories.css": "core",
+ "adminx/modules/Directories/assets/directories.js": "core",
+ "adminx/modules/Directories/migrations/001_create_directories.sql": "core",
+ "adminx/modules/Directories/module.php": "core",
+ "adminx/modules/Directories/view/index.twig": "core",
"adminx/modules/Documents/Controller.php": "core",
"adminx/modules/Documents/DocumentHookRunner.php": "core",
"adminx/modules/Documents/EditConflict.php": "core",
@@ -2052,9 +2132,11 @@
"adminx/modules/Documents/language/en/client.xml": "core",
"adminx/modules/Documents/language/en/interface.xml": "core",
"adminx/modules/Documents/language/en/runtime.xml": "core",
+ "adminx/modules/Documents/language/en/zz-interface.xml": "core",
"adminx/modules/Documents/language/ru/client.xml": "core",
"adminx/modules/Documents/language/ru/interface.xml": "core",
"adminx/modules/Documents/language/ru/runtime.xml": "core",
+ "adminx/modules/Documents/language/ru/zz-interface.xml": "core",
"adminx/modules/Documents/migrations/001_correct_breadcrumb_title.sql": "core",
"adminx/modules/Documents/migrations/002_correct_legacy_breadcrumb_title.sql": "core",
"adminx/modules/Documents/migrations/003_create_document_api_tokens.sql": "core",
@@ -2072,6 +2154,7 @@
"adminx/modules/Documents/migrations/015_document_creation_presets.sql": "core",
"adminx/modules/Documents/migrations/016_reconcile_document_content_columns.php": "core",
"adminx/modules/Documents/module.php": "core",
+ "adminx/modules/Documents/view/_product_panels.twig": "core",
"adminx/modules/Documents/view/api.twig": "core",
"adminx/modules/Documents/view/edit.twig": "core",
"adminx/modules/Documents/view/index.twig": "core",
@@ -2090,6 +2173,9 @@
"adminx/modules/Events/migrations/001_materialize_event_logs.sql": "core",
"adminx/modules/Events/module.php": "core",
"adminx/modules/Events/view/index.twig": "core",
+ "adminx/modules/GlobalSearch/Controller.php": "core",
+ "adminx/modules/GlobalSearch/Model.php": "core",
+ "adminx/modules/GlobalSearch/module.php": "core",
"adminx/modules/Groups/Controller.php": "core",
"adminx/modules/Groups/Model.php": "core",
"adminx/modules/Groups/Setup.php": "core",
@@ -2106,6 +2192,7 @@
"adminx/modules/Groups/migrations/003_seed_default_roles.sql": "core",
"adminx/modules/Groups/migrations/004_merge_legacy_roles.sql": "core",
"adminx/modules/Groups/migrations/005_register_development_site_permission.sql": "core",
+ "adminx/modules/Groups/migrations/006_normalize_control_panel_permission.sql": "core",
"adminx/modules/Groups/module.php": "core",
"adminx/modules/Groups/view/list.twig": "core",
"adminx/modules/Media/Controller.php": "core",
@@ -2148,23 +2235,26 @@
"adminx/modules/Navigation/migrations/001_create_navigation_tables.sql": "core",
"adminx/modules/Navigation/module.php": "core",
"adminx/modules/Navigation/view/index.twig": "core",
- "adminx/modules/Registrations/Controller.php": "core",
- "adminx/modules/Registrations/Model.php": "core",
- "adminx/modules/Registrations/Schema.php": "core",
- "adminx/modules/Registrations/assets/registrations.css": "core",
- "adminx/modules/Registrations/assets/registrations.js": "core",
- "adminx/modules/Registrations/language/en/client.xml": "core",
- "adminx/modules/Registrations/language/en/interface.xml": "core",
- "adminx/modules/Registrations/language/en/runtime.xml": "core",
- "adminx/modules/Registrations/language/ru/client.xml": "core",
- "adminx/modules/Registrations/language/ru/interface.xml": "core",
- "adminx/modules/Registrations/language/ru/runtime.xml": "core",
- "adminx/modules/Registrations/migrations/001_create_registration_certificates.sql": "core",
- "adminx/modules/Registrations/migrations/002_register_registration_permissions.sql": "core",
- "adminx/modules/Registrations/migrations/uninstall.sql": "core",
- "adminx/modules/Registrations/module.php": "core",
- "adminx/modules/Registrations/view/edit.twig": "core",
- "adminx/modules/Registrations/view/index.twig": "core",
+ "adminx/modules/PublicSite/Controller.php": "core",
+ "adminx/modules/PublicSite/Model.php": "core",
+ "adminx/modules/PublicSite/PresentationModel.php": "core",
+ "adminx/modules/PublicSite/PresentationsController.php": "core",
+ "adminx/modules/PublicSite/assets/presentations.css": "core",
+ "adminx/modules/PublicSite/assets/presentations.js": "core",
+ "adminx/modules/PublicSite/assets/public-site.css": "core",
+ "adminx/modules/PublicSite/language/en/client.xml": "core",
+ "adminx/modules/PublicSite/language/en/interface.xml": "core",
+ "adminx/modules/PublicSite/language/ru/client.xml": "core",
+ "adminx/modules/PublicSite/language/ru/interface.xml": "core",
+ "adminx/modules/PublicSite/migrations/001_create_presentations.sql": "core",
+ "adminx/modules/PublicSite/module.php": "core",
+ "adminx/modules/PublicSite/view/_placement_pagination.twig": "core",
+ "adminx/modules/PublicSite/view/_tabs.twig": "core",
+ "adminx/modules/PublicSite/view/diagnostics.twig": "core",
+ "adminx/modules/PublicSite/view/index.twig": "core",
+ "adminx/modules/PublicSite/view/map.twig": "core",
+ "adminx/modules/PublicSite/view/placements.twig": "core",
+ "adminx/modules/PublicSite/view/presentations.twig": "core",
"adminx/modules/Requests/Controller.php": "core",
"adminx/modules/Requests/Model.php": "core",
"adminx/modules/Requests/assets/requests.css": "core",
@@ -2210,6 +2300,8 @@
"adminx/modules/Rubrics/RubricFieldSetLinks.php": "core",
"adminx/modules/Rubrics/RubricRevisions.php": "core",
"adminx/modules/Rubrics/RubricSchemaImpact.php": "core",
+ "adminx/modules/Rubrics/assets/field-sets.css": "core",
+ "adminx/modules/Rubrics/assets/field-sets.js": "core",
"adminx/modules/Rubrics/assets/rubrics.css": "core",
"adminx/modules/Rubrics/assets/rubrics.js": "core",
"adminx/modules/Rubrics/language/en/interface.xml": "core",
@@ -2229,7 +2321,11 @@
"adminx/modules/Rubrics/migrations/011_schema_revisions.sql": "core",
"adminx/modules/Rubrics/migrations/012_linked_field_sets.sql": "core",
"adminx/modules/Rubrics/migrations/013_group_form_conditions.php": "core",
+ "adminx/modules/Rubrics/migrations/014_rubric_purpose.php": "core",
+ "adminx/modules/Rubrics/migrations/015_merge_directory_permissions.php": "core",
"adminx/modules/Rubrics/module.php": "core",
+ "adminx/modules/Rubrics/view/_content_tabs.twig": "core",
+ "adminx/modules/Rubrics/view/field-sets.twig": "core",
"adminx/modules/Rubrics/view/index.twig": "core",
"adminx/modules/Security/Controller.php": "core",
"adminx/modules/Security/assets/security.css": "core",
@@ -2333,8 +2429,10 @@
"help/README.md": "core",
"help/administration/README.md": "core",
"help/administration/console.md": "core",
+ "help/administration/content-and-system-tools.md": "core",
"help/administration/dashboard.md": "core",
"help/administration/operations.md": "core",
+ "help/administration/search-and-activity.md": "core",
"help/administration/settings.md": "core",
"help/administration/updates.md": "core",
"help/administration/users.md": "core",
@@ -2345,9 +2443,13 @@
"help/content/README.md": "core",
"help/content/blocks.md": "core",
"help/content/catalog.md": "core",
+ "help/content/content-model-tools.md": "core",
"help/content/content-studio.md": "core",
+ "help/content/directories.md": "core",
"help/content/documents.md": "core",
"help/content/navigation.md": "core",
+ "help/content/presentations.md": "core",
+ "help/content/public-site.md": "core",
"help/content/requests.md": "core",
"help/content/rubrics.md": "core",
"help/content/templates.md": "core",
@@ -2376,6 +2478,7 @@
"help/en/administration/console.md": "core",
"help/en/administration/dashboard.md": "core",
"help/en/administration/operations.md": "core",
+ "help/en/administration/search-and-activity.md": "core",
"help/en/administration/settings.md": "core",
"help/en/administration/updates.md": "core",
"help/en/administration/users.md": "core",
@@ -2386,9 +2489,13 @@
"help/en/content/README.md": "core",
"help/en/content/blocks.md": "core",
"help/en/content/catalog.md": "core",
+ "help/en/content/content-model-tools.md": "core",
"help/en/content/content-studio.md": "core",
+ "help/en/content/directories.md": "core",
"help/en/content/documents.md": "core",
"help/en/content/navigation.md": "core",
+ "help/en/content/presentations.md": "core",
+ "help/en/content/public-site.md": "core",
"help/en/content/requests.md": "core",
"help/en/content/rubrics.md": "core",
"help/en/content/templates.md": "core",
@@ -2459,6 +2566,7 @@
"help/en/modules/antispam.md": "core",
"help/en/modules/banners.md": "core",
"help/en/modules/benchmark.md": "core",
+ "help/en/modules/cache-warmup.md": "core",
"help/en/modules/comments.md": "core",
"help/en/modules/commerce.md": "core",
"help/en/modules/commerceml.md": "core",
@@ -2478,6 +2586,7 @@
"help/en/modules/interactions.md": "core",
"help/en/modules/legacy-migration.md": "core",
"help/en/modules/localization.md": "core",
+ "help/en/modules/mcp.md": "core",
"help/en/modules/migrations.md": "core",
"help/en/modules/module-php.md": "core",
"help/en/modules/not-found.md": "core",
@@ -2490,6 +2599,7 @@
"help/en/modules/popups.md": "core",
"help/en/modules/ratings.md": "core",
"help/en/modules/related.md": "core",
+ "help/en/modules/reliable-events.md": "core",
"help/en/modules/repository.md": "core",
"help/en/modules/reviews.md": "core",
"help/en/modules/rss.md": "core",
@@ -2550,6 +2660,7 @@
"help/modules/antispam.md": "core",
"help/modules/banners.md": "core",
"help/modules/benchmark.md": "core",
+ "help/modules/cache-warmup.md": "core",
"help/modules/comments.md": "core",
"help/modules/commerce.md": "core",
"help/modules/commerceml.md": "core",
@@ -2569,6 +2680,7 @@
"help/modules/interactions.md": "core",
"help/modules/legacy-migration.md": "core",
"help/modules/localization.md": "core",
+ "help/modules/mcp.md": "core",
"help/modules/migrations.md": "core",
"help/modules/module-php.md": "core",
"help/modules/not-found.md": "core",
@@ -2581,6 +2693,7 @@
"help/modules/popups.md": "core",
"help/modules/ratings.md": "core",
"help/modules/related.md": "core",
+ "help/modules/reliable-events.md": "core",
"help/modules/repository.md": "core",
"help/modules/reviews.md": "core",
"help/modules/rss.md": "core",
@@ -2711,7 +2824,10 @@
"system/App/Content/CatalogMenuRepository.php": "core",
"system/App/Content/CatalogTables.php": "core",
"system/App/Content/ContactsTables.php": "core",
+ "system/App/Content/ContentPlacementMap.php": "core",
"system/App/Content/ContentTables.php": "core",
+ "system/App/Content/ContentTagDependencies.php": "core",
+ "system/App/Content/Directories/DirectoryRepository.php": "core",
"system/App/Content/DocumentTerms.php": "core",
"system/App/Content/Documents/ContentCacheInvalidator.php": "core",
"system/App/Content/Documents/DocumentAliasRegistry.php": "core",
@@ -2726,11 +2842,13 @@
"system/App/Content/Documents/DocumentMediaFinalizer.php": "core",
"system/App/Content/Documents/DocumentMediaPath.php": "core",
"system/App/Content/Documents/DocumentMutationService.php": "core",
+ "system/App/Content/Documents/DocumentPickerRepository.php": "core",
"system/App/Content/Documents/DocumentRevisionPayload.php": "core",
"system/App/Content/Documents/DocumentRubricCodeRunner.php": "core",
"system/App/Content/Documents/DocumentSaveEvent.php": "core",
"system/App/Content/Documents/DocumentSaveEvents.php": "core",
"system/App/Content/Documents/DocumentSaveRejected.php": "core",
+ "system/App/Content/Documents/DocumentSearch.php": "core",
"system/App/Content/Documents/DocumentSnapshotBuilder.php": "core",
"system/App/Content/Documents/DocumentSnapshotRepository.php": "core",
"system/App/Content/Documents/DocumentSnapshotStore.php": "core",
@@ -2738,6 +2856,7 @@
"system/App/Content/Documents/RubricSchemaBuilder.php": "core",
"system/App/Content/ExtensionTables.php": "core",
"system/App/Content/Fields/AbstractFieldType.php": "core",
+ "system/App/Content/Fields/ComputedFieldEvaluator.php": "core",
"system/App/Content/Fields/DocumentMediaFieldTrait.php": "core",
"system/App/Content/Fields/DocumentMediaFieldType.php": "core",
"system/App/Content/Fields/DocumentRelationFieldTrait.php": "core",
@@ -2768,6 +2887,7 @@
"system/App/Content/Fields/Types/ChoiceValue.php": "core",
"system/App/Content/Fields/Types/Code.php": "core",
"system/App/Content/Fields/Types/ColorValue.php": "core",
+ "system/App/Content/Fields/Types/Computed.php": "core",
"system/App/Content/Fields/Types/ContactValue.php": "core",
"system/App/Content/Fields/Types/Content.php": "core",
"system/App/Content/Fields/Types/Date.php": "core",
@@ -2809,10 +2929,23 @@
"system/App/Content/Fields/Types/Teasers.php": "core",
"system/App/Content/Fields/Types/TextToImage.php": "core",
"system/App/Content/Fields/Types/Youtube.php": "core",
+ "system/App/Content/Introspection/ContentDiagnosis.php": "core",
+ "system/App/Content/Introspection/ContractRegistry.php": "core",
+ "system/App/Content/Introspection/IntrospectionSanitizer.php": "core",
+ "system/App/Content/Introspection/IntrospectionService.php": "core",
+ "system/App/Content/Introspection/PageContextInspector.php": "core",
"system/App/Content/Packages/ContentPackageWriter.php": "core",
"system/App/Content/Packages/PackageDependencyContext.php": "core",
"system/App/Content/Packages/PackageModuleProvisioner.php": "core",
"system/App/Content/PaymentPrograms.php": "core",
+ "system/App/Content/Presentation/DocumentPresentationBridge.php": "core",
+ "system/App/Content/Presentation/PresentationAssignmentRepository.php": "core",
+ "system/App/Content/Presentation/PresentationDataRegistry.php": "core",
+ "system/App/Content/Presentation/PresentationPreview.php": "core",
+ "system/App/Content/Presentation/PresentationRenderer.php": "core",
+ "system/App/Content/Presentation/PresentationRepository.php": "core",
+ "system/App/Content/Presentation/PresentationRevisionRepository.php": "core",
+ "system/App/Content/Presentation/PresentationSyntax.php": "core",
"system/App/Content/ProductShipping.php": "core",
"system/App/Content/PublicShellTables.php": "core",
"system/App/Content/PublicUserTables.php": "core",
@@ -2828,6 +2961,7 @@
"system/App/Content/Requests/RequestRuntimeValue.php": "core",
"system/App/Content/Requests/RequestTieBreakerAdvisor.php": "core",
"system/App/Content/Requests/RequestViewPreview.php": "core",
+ "system/App/Content/Revisions/JsonRevisionStore.php": "core",
"system/App/Content/Rubrics/FieldSetMerge.php": "core",
"system/App/Content/Rubrics/FieldSetRegistry.php": "core",
"system/App/Content/Rubrics/RubricFieldMutationService.php": "core",
diff --git a/README.md b/README.md
index 431015d..b50e09b 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# AVE.cms
-**AVE.cms 3.3 build 0.32** — модульная система управления сайтами на PHP.
+**AVE.cms 3.3 build 0.35** — модульная система управления сайтами на PHP.
Она подходит для проектов, в которых структуру содержимого нужно собирать из
собственных рубрик, полей, документов, запросов, блоков и шаблонов, не
привязываясь к заранее заданному типу сайта.
diff --git a/adminx/Support/BulkActionExecutor.php b/adminx/Support/BulkActionExecutor.php
new file mode 100644
index 0000000..6e60346
--- /dev/null
+++ b/adminx/Support/BulkActionExecutor.php
@@ -0,0 +1,91 @@
+
+ | @copyright 2007-2026 (c) AVE.cms
+ | @link https://ave-cms.ru
+ | @version 3.3
+ */
+
+ namespace App\Adminx\Support;
+
+ defined('BASEPATH') || die('Direct access to this location is not allowed.');
+
+ use App\Common\ErrorReport;
+
+ /**
+ * Единый безопасный цикл массовых административных действий.
+ *
+ * Контроллер по-прежнему отвечает за CSRF, права и аудит, а executor
+ * нормализует ID, ограничивает размер пачки и изолирует ошибку одной записи.
+ */
+ class BulkActionExecutor
+ {
+ public static function execute($action, $rawIds, array $actions, $limit = 200)
+ {
+ $action = trim((string) $action);
+ if ($action === '' || empty($actions[$action]) || !is_callable($actions[$action])) {
+ throw new \InvalidArgumentException('Неизвестное массовое действие');
+ }
+
+ $ids = self::normalizeIds($rawIds, $limit);
+ $result = array(
+ 'action' => $action,
+ 'requested' => count($ids),
+ 'done' => 0,
+ 'skipped' => 0,
+ 'errors' => array(),
+ );
+
+ foreach ($ids as $id) {
+ try {
+ $outcome = call_user_func($actions[$action], $id);
+ if ($outcome === false || (is_array($outcome) && isset($outcome['done']) && !$outcome['done'])) {
+ $result['skipped']++;
+ continue;
+ }
+
+ $result['done']++;
+ } catch (\Throwable $e) {
+ $result['errors'][] = '#' . $id . ': ' . ErrorReport::publicMessage(
+ 'Не удалось обработать запись',
+ $e,
+ 'BULK',
+ array('action' => $action, 'id' => $id)
+ );
+ }
+ }
+
+ return $result;
+ }
+
+ public static function normalizeIds($rawIds, $limit = 200)
+ {
+ $limit = max(1, min(1000, (int) $limit));
+ if (is_string($rawIds)) {
+ $rawIds = trim($rawIds);
+ $decoded = $rawIds !== '' && $rawIds[0] === '[' ? json_decode($rawIds, true) : null;
+ $rawIds = is_array($decoded) ? $decoded : preg_split('/[\s,;]+/', $rawIds, -1, PREG_SPLIT_NO_EMPTY);
+ }
+
+ $ids = array();
+ foreach (is_array($rawIds) ? $rawIds : array() as $rawId) {
+ $id = (int) $rawId;
+ if ($id > 0) {
+ $ids[$id] = $id;
+ }
+ }
+
+ $ids = array_values($ids);
+ if (!$ids || count($ids) > $limit) {
+ throw new \InvalidArgumentException('Выберите от 1 до ' . $limit . ' записей');
+ }
+
+ return $ids;
+ }
+ }
diff --git a/adminx/Support/GlobalSearchRegistry.php b/adminx/Support/GlobalSearchRegistry.php
new file mode 100644
index 0000000..3e1bd51
--- /dev/null
+++ b/adminx/Support/GlobalSearchRegistry.php
@@ -0,0 +1,188 @@
+
+ | @copyright 2007-2026 (c) AVE.cms
+ | @link https://ave-cms.ru
+ | @version 3.3
+ */
+
+ namespace App\Adminx\Support;
+
+ defined('BASEPATH') || die('Direct access to this location is not allowed.');
+
+ use App\Common\Permission;
+
+ /**
+ * Реестр провайдеров административного поиска.
+ *
+ * Provider получает ($query, $limit) и возвращает список безопасных
+ * admin-relative результатов. Ошибка одного provider не ломает палитру.
+ */
+ class GlobalSearchRegistry
+ {
+ protected static $providers = array();
+ protected static $reported = array();
+
+ public static function register($code, array $definition)
+ {
+ $code = self::code($code);
+ $provider = isset($definition['provider']) ? $definition['provider'] : null;
+ if ($code === '' || !is_callable($provider)) {
+ return false;
+ }
+
+ self::$providers[$code] = array(
+ 'code' => $code,
+ 'provider' => $provider,
+ 'permission' => isset($definition['permission']) ? trim((string) $definition['permission']) : '',
+ 'priority' => isset($definition['priority']) ? (int) $definition['priority'] : 100,
+ 'limit' => max(1, min(30, isset($definition['limit']) ? (int) $definition['limit'] : 8)),
+ );
+ return true;
+ }
+
+ public static function search($query, $limit = 30)
+ {
+ $query = trim(preg_replace('/\s+/u', ' ', (string) $query));
+ $limit = max(1, min(50, (int) $limit));
+ if (mb_strlen($query, 'UTF-8') < 2) {
+ return array();
+ }
+
+ $providers = array_values(self::$providers);
+ usort($providers, function ($left, $right) {
+ $order = (int) $left['priority'] <=> (int) $right['priority'];
+ return $order !== 0 ? $order : strnatcasecmp((string) $left['code'], (string) $right['code']);
+ });
+
+ $items = array();
+ foreach ($providers as $providerOrder => $definition) {
+ if ($definition['permission'] !== '' && !Permission::check($definition['permission'])) {
+ continue;
+ }
+
+ try {
+ $rows = call_user_func(
+ $definition['provider'],
+ $query,
+ min($definition['limit'], $limit)
+ );
+ } catch (\Throwable $e) {
+ self::report($definition['code'], $e);
+ continue;
+ }
+
+ $position = 0;
+ foreach (is_array($rows) ? $rows : array() as $row) {
+ $item = self::normalize($row, $definition['code']);
+ if (!$item) {
+ continue;
+ }
+
+ $item['_provider_order'] = $providerOrder;
+ $item['_position'] = $position++;
+ $items[] = $item;
+ }
+ }
+
+ usort($items, function ($left, $right) {
+ $provider = (int) $left['_provider_order'] <=> (int) $right['_provider_order'];
+ if ($provider !== 0) {
+ return $provider;
+ }
+
+ $score = (float) $right['score'] <=> (float) $left['score'];
+ return $score !== 0 ? $score : ((int) $left['_position'] <=> (int) $right['_position']);
+ });
+
+ $items = array_slice($items, 0, $limit);
+ foreach ($items as &$item) {
+ unset($item['_provider_order'], $item['_position'], $item['score']);
+ }
+
+ unset($item);
+ return $items;
+ }
+
+ public static function providers()
+ {
+ return array_keys(self::$providers);
+ }
+
+ public static function resetRuntime()
+ {
+ self::$providers = array();
+ self::$reported = array();
+ }
+
+ protected static function normalize($row, $providerCode)
+ {
+ if (!is_array($row)) {
+ return null;
+ }
+
+ $title = self::text(isset($row['title']) ? $row['title'] : '', 190);
+ $url = self::localUrl(isset($row['url']) ? $row['url'] : '');
+ if ($title === '' || $url === '') {
+ return null;
+ }
+
+ $icon = trim((string) (isset($row['icon']) ? $row['icon'] : 'ti ti-search'));
+ if (!preg_match('/^ti(?:\s+ti-[a-z0-9-]+)+$/', $icon)) {
+ $icon = 'ti ti-search';
+ }
+
+ return array(
+ 'type' => self::code(isset($row['type']) ? $row['type'] : $providerCode),
+ 'group' => self::text(isset($row['group']) ? $row['group'] : 'Результаты', 80),
+ 'title' => $title,
+ 'subtitle' => self::text(isset($row['subtitle']) ? $row['subtitle'] : '', 300),
+ 'url' => $url,
+ 'icon' => $icon,
+ 'score' => isset($row['score']) && is_numeric($row['score']) ? (float) $row['score'] : 0.0,
+ );
+ }
+
+ protected static function localUrl($url)
+ {
+ $url = trim((string) $url);
+ if ($url === ''
+ || $url[0] !== '/'
+ || strpos($url, '//') === 0
+ || strpos($url, '\\') !== false
+ || preg_match('/[\r\n\x00-\x1f]/', $url)) {
+ return '';
+ }
+
+ return substr($url, 0, 1000);
+ }
+
+ protected static function code($value)
+ {
+ $value = strtolower(trim((string) $value));
+ return substr(preg_replace('/[^a-z0-9_.-]+/', '_', $value), 0, 64);
+ }
+
+ protected static function text($value, $limit)
+ {
+ $value = html_entity_decode(strip_tags((string) $value), ENT_QUOTES, 'UTF-8');
+ $value = trim(preg_replace('/\s+/u', ' ', $value));
+ return mb_substr($value, 0, (int) $limit, 'UTF-8');
+ }
+
+ protected static function report($code, \Throwable $e)
+ {
+ if (isset(self::$reported[$code])) {
+ return;
+ }
+
+ self::$reported[$code] = true;
+ error_log('Global search provider ' . $code . ': ' . $e->getMessage());
+ }
+ }
diff --git a/adminx/Support/InterfaceSettings.php b/adminx/Support/InterfaceSettings.php
index f68a3af..ee395d7 100644
--- a/adminx/Support/InterfaceSettings.php
+++ b/adminx/Support/InterfaceSettings.php
@@ -133,6 +133,10 @@
return strcmp((string) $a['layout_code'], (string) $b['layout_code']);
});
+ if ($scope === 'navigation' && $config) {
+ $items = self::mergeNewNavigationItems($items);
+ }
+
foreach ($items as &$item) {
unset($item['_layout_position']);
}
@@ -148,6 +152,72 @@
}));
}
+ /**
+ * Новые пункты, которых ещё нет в сохранённой раскладке, остаются внутри
+ * своей группы по штатному sort_order, а не уходят в конец всего меню.
+ */
+ protected static function mergeNewNavigationItems(array $items)
+ {
+ $configured = array();
+ $added = array();
+ foreach ($items as $item) {
+ if ($item['_layout_position'] === null) {
+ $added[] = $item;
+ } else {
+ $configured[] = $item;
+ }
+ }
+
+ usort($added, array(__CLASS__, 'compareDefaultOrder'));
+ foreach ($added as $item) {
+ $insertAt = count($configured);
+ $lastPeer = null;
+ foreach ($configured as $index => $peer) {
+ if (!self::navigationPeers($item, $peer)) {
+ continue;
+ }
+
+ $lastPeer = $index;
+ if (self::compareDefaultOrder($item, $peer) < 0) {
+ $insertAt = $index;
+ break;
+ }
+
+ $insertAt = $index + 1;
+ }
+
+ if ($lastPeer === null) {
+ $insertAt = count($configured);
+ }
+
+ array_splice($configured, $insertAt, 0, array($item));
+ }
+
+ return $configured;
+ }
+
+ protected static function navigationPeers(array $left, array $right)
+ {
+ $leftParent = isset($left['parent']) ? (string) $left['parent'] : '';
+ $rightParent = isset($right['parent']) ? (string) $right['parent'] : '';
+ if ($leftParent !== '' || $rightParent !== '') {
+ return $leftParent !== '' && $leftParent === $rightParent;
+ }
+
+ return (string) $left['group'] === (string) $right['group'];
+ }
+
+ protected static function compareDefaultOrder(array $left, array $right)
+ {
+ $leftOrder = isset($left['sort_order']) ? (int) $left['sort_order'] : 1000;
+ $rightOrder = isset($right['sort_order']) ? (int) $right['sort_order'] : 1000;
+ if ($leftOrder !== $rightOrder) {
+ return $leftOrder < $rightOrder ? -1 : 1;
+ }
+
+ return strcmp((string) $left['layout_code'], (string) $right['layout_code']);
+ }
+
protected static function navigationHierarchy(array $items)
{
$roots = array();
diff --git a/adminx/Support/ModuleExtensions.php b/adminx/Support/ModuleExtensions.php
index 46da5a6..5f69fc7 100644
--- a/adminx/Support/ModuleExtensions.php
+++ b/adminx/Support/ModuleExtensions.php
@@ -51,6 +51,7 @@
);
if (!empty($module['installed']) && !empty($module['enabled'])) {
self::registerMenu($module['admin_extension']);
+ self::registerSearch($module['code'], $module['admin_extension']);
}
}
@@ -168,6 +169,64 @@
return $items;
}
+ /**
+ * Missing hard and recommended dependencies for the module owning a route.
+ */
+ public static function dependencyNotices($path)
+ {
+ $module = self::moduleForPath($path);
+ if (!$module) {
+ return array();
+ }
+
+ $descriptor = ModuleManager::descriptor($module['code']);
+ $required = is_array($descriptor) && isset($descriptor['requires'])
+ ? self::dependencyDefinitions($descriptor['requires'])
+ : array();
+ $recommended = is_array($descriptor) && isset($descriptor['recommends'])
+ ? self::dependencyDefinitions($descriptor['recommends'])
+ : array();
+ $requiredCodes = array();
+ $recommendedReasons = array();
+ foreach ($required as $definition) {
+ $requiredCodes[] = $definition['code'];
+ }
+
+ foreach ($recommended as $definition) {
+ $recommendedReasons[$definition['code']] = $definition['reason'];
+ }
+
+ $notices = array();
+ foreach (array(
+ 'required' => $requiredCodes,
+ 'recommended' => array_keys($recommendedReasons),
+ ) as $level => $codes) {
+ foreach ($codes as $code) {
+ $dependency = ModuleManager::get($code);
+ if ($dependency && !empty($dependency['installed']) && !empty($dependency['enabled'])) {
+ continue;
+ }
+
+ $name = $dependency && !empty($dependency['name']) ? (string) $dependency['name'] : (string) $code;
+ $state = !$dependency ? 'missing' : (empty($dependency['installed']) ? 'available' : 'disabled');
+ $reason = $level === 'recommended' && isset($recommendedReasons[$code])
+ ? $recommendedReasons[$code]
+ : '';
+ $notices[] = array(
+ 'level' => $level,
+ 'code' => (string) $code,
+ 'name' => $name,
+ 'state' => $state,
+ 'reason' => $reason,
+ 'action' => $state === 'disabled' ? 'Включить модуль' : ($state === 'missing' ? 'Найти в каталоге' : 'Установить модуль'),
+ 'url' => '/modules?' . ($state === 'missing' ? 'tab=catalog&' : '') . 'focus=' . rawurlencode((string) $code),
+ );
+ }
+ }
+
+ return $notices;
+ }
+
protected static function contributions($type, array $visibleCodes = null)
{
self::boot();
@@ -250,6 +309,24 @@
}
}
+ protected static function registerSearch($moduleCode, array $config)
+ {
+ if (empty($config['search'])) {
+ return;
+ }
+
+ foreach (self::normalizeItems($config['search']) as $index => $definition) {
+ if (!is_array($definition)) {
+ continue;
+ }
+
+ $code = isset($definition['code']) && trim((string) $definition['code']) !== ''
+ ? (string) $definition['code']
+ : (string) ((int) $index + 1);
+ GlobalSearchRegistry::register($moduleCode . '.' . $code, $definition);
+ }
+ }
+
protected static function normalizeItems($items)
{
if (!is_array($items)) {
@@ -285,6 +362,49 @@
return $fallback;
}
+ protected static function moduleForPath($path)
+ {
+ $path = '/' . trim((string) $path, '/');
+ $match = null;
+ $matchLength = -1;
+ foreach (ModuleManager::all() as $module) {
+ $extension = isset($module['admin_extension']) && is_array($module['admin_extension'])
+ ? $module['admin_extension']
+ : array();
+ $url = isset($extension['url']) ? '/' . trim((string) $extension['url'], '/') : '';
+ if ($url === '' || ($path !== $url && strpos($path, $url . '/') !== 0) || strlen($url) <= $matchLength) {
+ continue;
+ }
+
+ $match = $module;
+ $matchLength = strlen($url);
+ }
+
+ return $match;
+ }
+
+ protected static function dependencyDefinitions($dependencies)
+ {
+ $result = array();
+ foreach (is_array($dependencies) ? $dependencies : array() as $dependency) {
+ $code = is_array($dependency) && isset($dependency['code'])
+ ? strtolower(trim((string) $dependency['code']))
+ : strtolower(trim((string) $dependency));
+ if ($code === '') {
+ continue;
+ }
+
+ $result[] = array(
+ 'code' => $code,
+ 'reason' => is_array($dependency) && isset($dependency['reason'])
+ ? trim((string) $dependency['reason'])
+ : '',
+ );
+ }
+
+ return $result;
+ }
+
/** Управляемая администратором видимость UI-вклада модуля. */
protected static function presentationEnabled($code, $type)
{
diff --git a/adminx/Support/SectionHelp.php b/adminx/Support/SectionHelp.php
index 95796fe..96dcf46 100644
--- a/adminx/Support/SectionHelp.php
+++ b/adminx/Support/SectionHelp.php
@@ -80,6 +80,10 @@
self::item('#^/documents/views$#', 'Статистика просмотров', 'Показывает посещаемость документов по дням и помогает находить наиболее востребованные страницы.', array('Период влияет только на подневную статистику.', 'Общий счётчик документа хранится отдельно и не очищается вместе с дневными данными.', 'Очистка статистики необратима и предназначена для обслуживания.'), 'Для бизнес-аналитики используйте внешнюю систему аналитики; здесь хранится внутренняя статистика CMS.'),
self::item('#^/documents/(?:create|\d+/edit)$#', 'Редактор документа', 'Создаёт и изменяет страницу сайта: её адрес, публикацию, SEO и значения полей выбранной рубрики.', array('Набор полей и их группы задаются рубрикой.', 'Изменение alias может автоматически создать редирект со старого URL.', 'Ctrl+S сохраняет изменения и оставляет редактор открытым; ревизии позволяют вернуться к прошлой версии.'), 'Смена рубрики существующего документа запрещена без отдельной миграции полей.'),
self::item('#^/documents#', 'Документы', 'Основной реестр страниц и товарных карточек сайта, построенных на рубриках и их полях.', array('Фильтруйте документы по рубрике, состоянию и названию.', 'Публикацию, копирование и удаление можно выполнять из списка или пакетно.', 'Удалённый документ сначала попадает в корзину и только затем может быть удалён окончательно.'), 'Документ №1 и системная страница 404 защищены от окончательного удаления.', 'content/documents'),
+ self::item('#^/public-site/presentations$#', 'Представления', 'Создаёт повторно используемое оформление карточек, списков, слайдеров и других результатов.', array('Черновик проверяется на реальном материале и не меняет публичный сайт.', 'Публикация фиксирует версию, а назначение выбирает место и режим применения.', 'Представление меняет внешний вид готовых данных, но не заменяет рубрику, запрос, блок или модуль.'), 'Для совместимости каждое новое назначение начинает работу в режиме «Текущий вывод».', 'content/public-site'),
+ self::item('#^/public-site/map$#', 'Визуальная карта сайта', 'Показывает деревья меню, связанные страницы и опубликованные документы вне навигации.', array('Уровни строятся из действующих пунктов навигации.', 'Ссылка документа открывает его штатный редактор.', 'Документ вне меню может использоваться запросом, каталогом или прямой ссылкой.'), 'Карта является обзором и ничего не публикует.', 'content/public-site'),
+ self::item('#^/public-site/placements$#', 'Размещения компонентов', 'Показывает, в каких сохранённых шаблонах используются блоки, подборки, навигации и модульные теги.', array('Вкладка «Размещения» показывает каждое найденное место отдельно.', 'Вкладка «Где используется» объединяет одинаковые компоненты и показывает число связей.', 'Кнопки справа открывают конкретный исходный шаблон и конкретный подключённый компонент.'), 'Карта предназначена для поиска связей и ничего не меняет на публичном сайте.', 'content/public-site'),
+ self::item('#^/public-site(?:/.*)?$#', 'Публичный сайт', 'Показывает, из каких базовых компонентов собирается сайт и где используется каждый из них.', array('Раздел является картой и не дублирует редакторы шаблонов, рубрик, запросов, блоков и меню.', 'Представления — дополнительный способ оформить уже подготовленные данные.', 'Диагностика сообщает только о явных пробелах в обязательных настройках.'), 'Начните со вкладки «Структура», а содержимое меняйте в штатном редакторе нужного компонента.', 'content/public-site'),
self::item('#^/catalog/products/shipping$#', 'Доставка и упаковка', 'Показывает готовность товарных данных к расчёту доставки и позволяет редактировать грузовые места без открытия документа.', array('Каждое грузовое место хранит количество, вес и три габарита.', 'Расчёт включается отдельно и использует только полностью заполненную упаковку.', 'Фильтр помогает быстро найти товары без размеров или с выключенным расчётом.'), 'Вес и размеры указываются для одного грузового места; количество умножает итоговые показатели.'),
self::item('#^/catalog/products(?:/.*)?$#', 'Товары каталога', 'Показывает товарные документы в быстром индексе каталога: цены, остатки, изображения и категории.', array('Переиндексация обновляет проекцию товара из полей документа.', 'Редактор товара остаётся редактором документа, но открывается из каталога.', 'Фильтры списка не меняют данные товара.'), 'После изменения каталоговых полей индекс обычно обновляется автоматически.'),
@@ -90,6 +94,8 @@
self::item('#^/catalog/\d+/\d+$#', 'Конструктор каталога', 'Связывает поле типа catalog с деревом разделов, документами, полями и публичными фильтрами.', array('Разделы задают структуру каталога и набор доступных полей.', 'Условия фильтров определяют SQL-ограничения публичной выборки.', 'Порядок полей и фильтров меняется перетаскиванием и сохраняется сразу.'), 'Изменения структуры влияют на редактор документов и публичный каталог.'),
self::item('#^/catalog#', 'Каталог', 'Управляет структурами каталогов, товарным индексом, фильтрами и связью с документами.', array('Каждый каталог создаётся для конкретного поля рубрики типа catalog.', 'Товары являются документами и не дублируются в отдельной основной таблице.', 'Для вариантов одного товара используются группы вариантов.'), 'Начинайте с выбора каталога, затем настраивайте разделы, поля и фильтры.', 'content/catalog'),
+ self::item('#^/rubrics/field-sets$#', 'Библиотека наборов полей', 'Добавляет в рубрику готовую структуру полей для типовой задачи.', array('Копия создаёт независимые поля.', 'Связанный режим позволяет позже синхронизировать набор вручную.', 'Перед применением проверяются существующие alias.'), 'После применения настройте порядок и ширину полей в конструкторе рубрики.', 'content/content-model-tools'),
+ self::item('#^/directories(?:/.*)?$#', 'Справочники', 'Управляет повторно используемыми значениями через обычные рубрики и документы.', array('Сначала создайте справочник и его поля.', 'Затем наполните его документами-значениями.', 'В рабочей рубрике выберите справочник источником поля связи.'), 'Справочник использует штатную модель документов и не создаёт параллельное хранилище.', 'content/content-model-tools'),
self::item('#^/rubrics(?:/.*)?$#', 'Рубрики и поля', 'Определяет типы документов: набор полей, группы, шаблоны вывода, права и код жизненного цикла.', array('Поля являются отдельными сущностями со своим типом, настройками и шаблоном.', 'Группы организуют длинные формы документов и не влияют на публичный URL.', 'Код до/после сохранения выполняется при изменении документа и требует проверки синтаксиса.'), 'Удаляйте рубрики и поля только после проверки связанных документов и шаблонов.', 'content/rubrics'),
self::item('#^/themes#', 'Темы публичного сайта', 'Управляет файлами оформления, подключениями CSS/JavaScript и способом сборки публичной страницы.', array('Файлы темы находятся в templates/<код темы> и проходят проверку пути и расширения.', 'Во вкладке «Настройки» выбирается нативная сборка AVE.cms или Twig-оболочка темы.', 'Текстовые файлы получают ревизии; тему можно экспортировать или импортировать безопасным ZIP-пакетом.'), 'Ассеты и переопределения темы работают в обоих режимах сборки.', 'content/themes'),
self::item('#^/templates#', 'Шаблоны страниц', 'Хранит внешнюю HTML/PHP-оболочку сайта, в которую подставляется содержимое документа и системные теги.', array('Шаблон №1 является системным и не удаляется.', 'Сохранение обновляет БД, файловый кеш и создаёт ревизию.', 'Палитра тегов вставляет зарегистрированные блоки, навигации и значения страницы.'), 'Ошибка PHP в основном шаблоне может нарушить весь публичный сайт — используйте проверку перед сохранением.', 'content/templates'),
@@ -113,7 +119,7 @@
self::item('#^/settings/security$#', 'Подтверждение критических действий', 'Управляет повторным запросом пароля перед изменением исполняемого кода и установкой модулей.', array('Подтверждение действует пять минут для текущего пользователя и IP-адреса.', 'Отключение повторного пароля не отменяет проверку прав роли и CSRF-токена.', 'Настройка распространяется на блоки, шаблоны, рубрики, запросы, навигацию и другие операции с кодом.'), 'По умолчанию подтверждение выключено; на рабочем сайте его можно включить как дополнительный защитный слой.'),
self::item('#^/settings/constants$#', 'Системные константы', 'Хранит типизированные значения конфигурации, доступные ядру и публичному сайту.', array('Тип определяет допустимый редактор и формат сохранённого значения.', 'Перед добавлением проверьте, нет ли уже настройки с тем же назначением.', 'Удаляйте константу только после поиска её использования в коде и шаблонах.'), 'Название константы является программным контрактом и не должно меняться без миграции.'),
self::item('#^/settings/paginations$#', 'Шаблоны пагинации', 'Настраивает HTML постраничной навигации для документов и сохранённых запросов.', array('Базовый шаблон защищён от удаления.', 'Теги страниц, текущего состояния и ссылок подставляются во время рендера.', 'После изменения проверьте первую, среднюю и последнюю страницы списка.'), 'Шаблон должен сохранять доступность ссылок и понятное обозначение текущей страницы.'),
- self::item('#^/settings/maintenance$#', 'Обслуживание', 'Очищает адресные кеши и накопленные служебные данные AVE.cms.', array('Каждая кнопка работает только со своим источником данных.', 'Очистка кеша не удаляет документы и настройки.', 'Ревизии и статистика просмотров удаляются необратимо.'), 'Перед очисткой данных, которые нельзя восстановить из документов, создайте резервную копию.'),
+ self::item('#^/settings/maintenance$#', 'Обслуживание', 'Очищает адресные кеши и накопленные служебные данные AVE.cms.', array('Каждая кнопка работает только со своим источником данных.', 'Очистка кеша не удаляет документы и настройки.', 'Очистка ревизий удаляет сохранённые версии документов, блоков, шаблонов, рубрик, тем, представлений, карточек товаров и фильтров каталога.', 'Аудит действий, история заказов и обращения этой операцией не затрагиваются.'), 'Ревизии и статистика просмотров удаляются необратимо — перед очисткой создайте резервную копию.'),
self::item('#^/settings/diagnostics$#', 'Диагностика системы', 'Проверяет PHP, расширения, доступ к каталогам, почту, данные модулей и готовность публичной части.', array('Проверки не отправляют письма и не выполняют внешние операции.', 'Красные состояния нужно устранить до публикации.', 'Тяжёлые проверки могут кратковременно кешироваться.'), 'Диагностика ядра не заменяет отдельную проверку подключённых модулей и внешних сервисов.'),
self::item('#^/settings/files$#', 'Системные файлы', 'Даёт контролируемый редактор разрешённых PHP-файлов конфигурации и пользовательских функций.', array('Список файлов ограничен системой и не является файловым менеджером.', 'Перед сохранением выполняется проверка PHP-синтаксиса.', 'Ошибка в таком файле может повлиять и на панель, и на публичный сайт.'), 'Используйте этот раздел для небольших проектных дополнений; крупную функциональность оформляйте модулем.'),
self::item('#^/settings/benchmark$#', 'Производительность', 'Измеряет скорость PHP, базы данных, файловых операций и кеша на текущем сервере.', array('Результаты нужны для сравнения одного окружения во времени, а не разных проектов.', 'Повторяйте тест после обновления PHP, БД или инфраструктуры.', 'Тест создаёт кратковременную нагрузку и сохраняет историю запусков.'), 'Оценивайте не только общий балл, но и конкретный медленный этап.'),
diff --git a/adminx/assets/css/adminkit.css b/adminx/assets/css/adminkit.css
index 71a85c6..e7e6b82 100644
--- a/adminx/assets/css/adminkit.css
+++ b/adminx/assets/css/adminkit.css
@@ -520,7 +520,6 @@ hr {
background: var(--color-danger);
border-radius: 999px;
border: 2px solid var(--background-page);
- font-variant-numeric: tabular-nums;
}
.notif-menu {
width: 330px;
@@ -579,7 +578,6 @@ hr {
font-weight: 700;
font-size: 13px;
color: var(--text-secondary);
- font-variant-numeric: tabular-nums;
}
.notif-empty {
padding: 30px 16px;
@@ -594,6 +592,13 @@ hr {
color: var(--color-success);
}
/* Заголовок в меню пользователя */
+#userMenu > .dropdown-menu {
+ width: 280px;
+ max-width: calc(100vw - 16px);
+}
+#userMenu #themeToggle [data-theme-label] {
+ white-space: nowrap;
+}
.dd-user {
display: flex;
align-items: center;
@@ -1805,7 +1810,6 @@ hr {
}
.ax-list-head > .badge {
flex: 0 0 auto;
- font-variant-numeric: tabular-nums;
}
@media (max-width: 680px) {
.ax-list-head {
@@ -2652,6 +2656,132 @@ input[type="color"].color-input {
right: auto;
}
}
+.command-palette {
+ position: fixed;
+ z-index: 1300;
+ inset: 0;
+}
+.command-palette-backdrop {
+ position: absolute;
+ inset: 0;
+ background: rgba(15, 23, 42, 0.42);
+ backdrop-filter: blur(2px);
+}
+.command-palette-dialog {
+ position: relative;
+ width: min(680px, calc(100vw - 32px));
+ max-height: min(620px, calc(100vh - 96px));
+ margin: 72px auto 0;
+ overflow: hidden;
+ border-radius: 8px;
+ background: var(--background-surface, #fff);
+ box-shadow: 0 24px 70px rgba(15, 23, 42, 0.28), 0 4px 18px rgba(15, 23, 42, 0.14);
+}
+.command-palette-input {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr) auto;
+ align-items: center;
+ gap: var(--space-3);
+ min-height: 62px;
+ padding: 0 var(--space-3) 0 var(--space-5);
+ border-bottom: 1px solid var(--border-default);
+ background: inherit;
+}
+.command-palette-input > .ti {
+ color: var(--text-secondary);
+ font-size: 20px;
+}
+.command-palette-input input {
+ width: 100%;
+ border: 0;
+ outline: 0;
+ background: transparent;
+ color: var(--text-primary);
+ font: inherit;
+ font-size: 16px;
+}
+.command-palette-input input:focus,
+.command-palette-input input:focus-visible {
+ outline: 0;
+ box-shadow: none;
+}
+.command-palette-input input::-webkit-search-cancel-button {
+ display: none;
+ -webkit-appearance: none;
+ appearance: none;
+}
+.command-palette-results {
+ max-height: min(550px, calc(100vh - 160px));
+ overflow-y: auto;
+ padding: var(--space-2);
+ background: inherit;
+}
+.command-palette-group {
+ padding: var(--space-2) var(--space-3) var(--space-1);
+ color: var(--text-secondary);
+ font-size: 11px;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+.command-palette-item {
+ display: grid;
+ grid-template-columns: 36px minmax(0, 1fr) auto;
+ align-items: center;
+ gap: var(--space-3);
+ width: 100%;
+ min-height: 52px;
+ padding: var(--space-2) var(--space-3);
+ border: 0;
+ border-radius: 6px;
+ background: transparent;
+ color: var(--text-primary);
+ text-align: left;
+ cursor: pointer;
+}
+.command-palette-item:hover,
+.command-palette-item.is-active {
+ background: var(--blue-50);
+ color: var(--blue-700);
+}
+.command-palette-item .icon-tile {
+ width: 36px;
+ height: 36px;
+}
+.command-palette-copy {
+ display: grid;
+ gap: 2px;
+ min-width: 0;
+}
+.command-palette-copy b,
+.command-palette-copy small {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.command-palette-copy small {
+ color: var(--text-secondary);
+}
+.command-palette-empty {
+ display: grid;
+ place-items: center;
+ gap: var(--space-2);
+ min-height: 180px;
+ color: var(--text-secondary);
+ text-align: center;
+}
+.command-palette-empty .ti {
+ font-size: 28px;
+}
+body.command-palette-open {
+ overflow: hidden;
+}
+@media (max-width: 720px) {
+ .command-palette-dialog {
+ width: calc(100vw - 16px);
+ max-height: calc(100vh - 24px);
+ margin-top: 12px;
+ }
+}
/* ============================================================
9. ТАБЛИЦЫ
============================================================ */
@@ -2863,7 +2993,6 @@ table.table {
cursor: pointer;
display: inline-grid;
place-items: center;
- font-variant-numeric: tabular-nums;
transition: border-color 0.12s, color 0.12s, background-color 0.12s;
}
.pagination .page:hover,
@@ -3044,6 +3173,14 @@ table.table {
.modal-footer .mf-left {
margin-right: auto;
}
+.confirm-modal-body {
+ padding-top: var(--space-4);
+ padding-bottom: var(--space-5);
+}
+.confirm-modal-body p {
+ margin: 0;
+ line-height: 1.55;
+}
.dialog-icon {
width: 42px;
height: 42px;
@@ -3571,6 +3708,34 @@ table.table {
[data-theme="dark"] .alert-integration {
color: #67e8f9;
}
+.module-dependency-notices {
+ display: grid;
+ gap: 8px;
+ margin-bottom: 16px;
+}
+.module-dependency-notices .alert {
+ align-items: center;
+}
+.module-dependency-copy {
+ display: grid;
+ gap: 2px;
+ min-width: 0;
+}
+.module-dependency-notices .btn {
+ margin-left: auto;
+ flex: 0 0 auto;
+}
+@media (max-width: 640px) {
+ .module-dependency-notices .alert {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr);
+ }
+ .module-dependency-notices .btn {
+ grid-column: 1/-1;
+ width: 100%;
+ margin-left: 0;
+ }
+}
.banner {
display: flex;
align-items: center;
@@ -4965,8 +5130,8 @@ ul.sortable {
}
.media-picker-grid {
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
- gap: 12px;
+ grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
+ gap: 10px;
grid-auto-rows: max-content;
flex: 1 1 auto;
align-content: start;
@@ -4975,7 +5140,7 @@ ul.sortable {
}
.media-picker-item {
display: grid;
- grid-template-rows: auto minmax(62px, auto);
+ grid-template-rows: auto minmax(54px, auto);
gap: 0;
min-width: 0;
padding: 0;
@@ -5025,7 +5190,7 @@ ul.sortable {
align-content: center;
gap: 4px;
min-width: 0;
- padding: 10px 11px;
+ padding: 8px 9px;
background: var(--background-card);
}
.media-picker-item b,
@@ -5072,7 +5237,7 @@ ul.sortable {
}
.media-picker-item.is-folder {
grid-template-columns: none;
- grid-template-rows: auto minmax(62px, auto);
+ grid-template-rows: auto minmax(54px, auto);
align-items: stretch;
gap: 0;
min-height: 0;
@@ -5080,7 +5245,7 @@ ul.sortable {
}
.media-picker-item.is-folder .media-picker-meta {
align-content: center;
- padding: 10px 11px;
+ padding: 8px 9px;
background: var(--background-card);
}
.media-picker-folder-icon {
@@ -5102,6 +5267,28 @@ ul.sortable {
font-size: 12px;
color: var(--text-secondary);
}
+@media (max-width: 640px) {
+ .media-picker {
+ width: 100%;
+ height: 94vh;
+ max-height: 94vh;
+ }
+ .media-picker-tools {
+ flex-wrap: wrap;
+ }
+ .media-picker-tools .media-picker-search {
+ flex-basis: calc(100% - 50px);
+ }
+ .media-picker-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+ .media-picker .modal-footer {
+ padding: 12px 14px;
+ }
+ .media-picker-count {
+ flex-basis: 100%;
+ }
+}
/* ============================================================
14. БОЛЬШИЕ ФОРМЫ / FORM BUILDER / CMS / ПРОЧЕЕ
============================================================ */
@@ -6422,7 +6609,6 @@ body.sidebar-collapsed .sidebar .nav-sub.flyout-open .nav-item .nav-count {
font-size: 20px;
font-weight: 800;
font-family: var(--font-display, inherit);
- font-variant-numeric: tabular-nums;
line-height: 1.1;
}
.stat-label {
diff --git a/adminx/assets/js/adminx.js b/adminx/assets/js/adminx.js
index 318b6cb..f332fe0 100644
--- a/adminx/assets/js/adminx.js
+++ b/adminx/assets/js/adminx.js
@@ -591,7 +591,7 @@
overlay.className = 'overlay';
var modal = document.createElement('div');
- modal.className = 'modal';
+ modal.className = 'modal confirm-modal';
modal.setAttribute('role', 'dialog');
modal.setAttribute('aria-modal', 'true');
@@ -600,10 +600,10 @@
' ' +
'
' +
'
' + esc(Adminx.tr(cfg.title || Adminx.t('confirm_title', 'Подтвердите действие'))) + ' ' +
- (cfg.message ? '
' + esc(Adminx.tr(cfg.message)) + '
' : '') +
'
' +
' ' +
'' +
+ (cfg.message ? '' + esc(Adminx.tr(cfg.message)) + '
' : '') +
'
-
+
{% endblock %}
diff --git a/adminx/modules/Blocks/Model.php b/adminx/modules/Blocks/Model.php
index 1bf9f38..aeb060c 100644
--- a/adminx/modules/Blocks/Model.php
+++ b/adminx/modules/Blocks/Model.php
@@ -285,6 +285,16 @@
return false;
}
+ $dependencies = \App\Content\ContentTagDependencies::block(
+ (int) $id,
+ isset($raw['sysblock_alias']) ? (string) $raw['sysblock_alias'] : ''
+ );
+ if ($dependencies) {
+ throw new \RuntimeException(
+ 'Блок используется: ' . implode(', ', $dependencies) . '. Сначала уберите эти вызовы.'
+ );
+ }
+
Revisions::capture((int) $id, 'delete', (int) $authorId, 'Удаление блока', $raw);
$row = self::row($raw);
if (!$row) {
diff --git a/adminx/modules/Blocks/Revisions.php b/adminx/modules/Blocks/Revisions.php
index 7789f0c..0138208 100644
--- a/adminx/modules/Blocks/Revisions.php
+++ b/adminx/modules/Blocks/Revisions.php
@@ -17,12 +17,13 @@
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use DB;
- use App\Common\SystemTables;
use App\Content\ContentTables;
- use App\Helpers\Json;
+ use App\Content\Revisions\JsonRevisionStore;
class Revisions
{
+ protected static $store;
+
public static function table()
{
return ContentTables::table('sysblock_revisions');
@@ -43,14 +44,8 @@
public static function listForBlock($blockId, $limit = 50)
{
- $rows = DB::query(
- 'SELECT * FROM ' . self::table() . ' WHERE block_id = %i ORDER BY created_at DESC, id DESC LIMIT %i',
- (int) $blockId,
- max(1, min(200, (int) $limit))
- )->getAll();
-
$out = array();
- foreach ($rows as $row) {
+ foreach (self::store()->listing($blockId, $limit) as $row) {
$out[] = self::format($row, false);
}
@@ -59,7 +54,7 @@
public static function one($id)
{
- $row = DB::query('SELECT * FROM ' . self::table() . ' WHERE id = %i LIMIT 1', (int) $id)->getAssoc();
+ $row = self::store()->one($id);
return $row ? self::format($row, true) : null;
}
@@ -75,38 +70,19 @@
}
$snapshot = Model::snapshot($snapshot);
- $json = Json::encode($snapshot, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
- if ($json === false) {
- $json = '{}';
- }
-
- $snapshotHash = sha1($json);
$textHash = sha1((string) $snapshot['sysblock_text']);
-
- if (in_array($action, array('update', 'import', 'restore'), true)) {
- $last = DB::query(
- 'SELECT snapshot_hash FROM ' . self::table() . ' WHERE block_id = %i ORDER BY created_at DESC, id DESC LIMIT 1',
- $blockId
- )->getValue();
- if ($last && (string) $last === $snapshotHash) {
- return 0;
- }
- }
-
- DB::Insert(self::table(), array(
- 'block_id' => $blockId,
- 'action' => (string) $action,
- 'snapshot_hash' => $snapshotHash,
+ return self::store()->capture(
+ $blockId,
+ $action,
+ $snapshot,
+ $authorId,
+ $comment,
+ array(
'text_hash' => $textHash,
- 'snapshot_json' => $json,
- 'comment' => trim((string) $comment),
- 'author_id' => (int) $authorId,
- 'author_name' => self::authorName((int) $authorId),
'source_revision_id' => (int) $sourceRevisionId,
- 'created_at' => time(),
- ));
-
- return (int) DB::insertId();
+ ),
+ in_array($action, array('update', 'import', 'restore'), true)
+ );
}
public static function captureCurrent($action, $authorId = 0, $comment = '')
@@ -144,98 +120,47 @@
public static function delete($revisionId)
{
- $revision = self::one($revisionId);
- if (!$revision) {
- return false;
- }
-
- DB::Delete(self::table(), 'id = %i', (int) $revisionId);
- return (int) $revision['block_id'];
+ return self::store()->delete($revisionId);
}
public static function deleteForBlock($blockId)
{
- $blockId = (int) $blockId;
- if ($blockId <= 0) {
- return 0;
- }
-
- $count = (int) DB::query('SELECT COUNT(*) FROM ' . self::table() . ' WHERE block_id = %i', $blockId)->getValue();
- DB::Delete(self::table(), 'block_id = %i', $blockId);
- return $count;
+ return self::store()->deleteFor($blockId);
}
protected static function format(array $row, $withSnapshot)
{
- $labels = self::labels();
- $action = isset($row['action']) ? (string) $row['action'] : 'update';
- $meta = isset($labels[$action]) ? $labels[$action] : array('label' => $action, 'badge' => 'badge-gray');
- $snapshot = null;
- if ($withSnapshot) {
- $snapshot = Json::toArray((string) $row['snapshot_json']);
- }
-
- $created = isset($row['created_at']) ? (int) $row['created_at'] : 0;
+ $row = self::store()->formatRow($row, $withSnapshot);
+ $snapshot = $row['snapshot'];
$text = $withSnapshot && is_array($snapshot) && isset($snapshot['sysblock_text']) ? (string) $snapshot['sysblock_text'] : '';
return array(
'id' => (int) $row['id'],
'block_id' => (int) $row['block_id'],
- 'action' => $action,
- 'action_label' => $meta['label'],
- 'badge' => $meta['badge'],
+ 'action' => $row['action'],
+ 'action_label' => $row['action_label'],
+ 'badge' => $row['badge'],
'comment' => (string) $row['comment'],
'author_id' => (int) $row['author_id'],
'author_name' => (string) $row['author_name'],
- 'created_at' => $created,
- 'created_label' => $created > 0 ? date('d.m.Y H:i:s', $created) : '-',
+ 'created_at' => $row['created_at'],
+ 'created_label' => $row['created_label'],
'source_revision_id' => (int) $row['source_revision_id'],
'snapshot_hash' => (string) $row['snapshot_hash'],
'text_hash' => (string) $row['text_hash'],
'text_size' => strlen($text),
- 'text_size_label' => $withSnapshot ? self::formatBytes(strlen($text)) : '',
+ 'text_size_label' => $withSnapshot ? JsonRevisionStore::formatBytes(strlen($text)) : '',
'snapshot' => $snapshot,
'code' => $text,
);
}
- protected static function authorName($id)
+ protected static function store()
{
- if ((int) $id <= 0) {
- return '';
+ if (!self::$store) {
+ self::$store = new JsonRevisionStore(self::table(), 'block_id', self::labels());
}
- $row = DB::query('SELECT name, login, email FROM ' . SystemTables::table('users') . ' WHERE id = %i LIMIT 1', (int) $id)->getAssoc();
- if (!$row) {
- return '#' . (int) $id;
- }
-
- if (!empty($row['name'])) {
- return (string) $row['name'];
- }
-
- if (!empty($row['login'])) {
- return '@' . (string) $row['login'];
- }
-
- if (!empty($row['email'])) {
- return (string) $row['email'];
- }
-
- return '#' . (int) $id;
- }
-
- protected static function formatBytes($bytes)
- {
- $bytes = (int) $bytes;
- if ($bytes < 1024) {
- return $bytes . ' Б';
- }
-
- if ($bytes < 1048576) {
- return round($bytes / 1024, 1) . ' KB';
- }
-
- return round($bytes / 1048576, 1) . ' MB';
+ return self::$store;
}
}
diff --git a/adminx/modules/Blocks/assets/blocks.css b/adminx/modules/Blocks/assets/blocks.css
index 4daacb0..bce7e32 100644
--- a/adminx/modules/Blocks/assets/blocks.css
+++ b/adminx/modules/Blocks/assets/blocks.css
@@ -19,7 +19,6 @@
font-size: 20px;
line-height: 1.1;
font-weight: 800;
- font-variant-numeric: tabular-nums;
}
.blocks-stat span {
font-size: 13px;
@@ -212,7 +211,6 @@
gap: 2px;
color: var(--text-secondary);
font-size: 12px;
- font-variant-numeric: tabular-nums;
}
.blocks-actions {
gap: 4px;
diff --git a/adminx/modules/Blocks/assets/blocks.js b/adminx/modules/Blocks/assets/blocks.js
index 9980cee..d62e998 100644
--- a/adminx/modules/Blocks/assets/blocks.js
+++ b/adminx/modules/Blocks/assets/blocks.js
@@ -151,6 +151,8 @@
applyFilters: function (form, push) {
if (!form) { return; }
+ clearTimeout(this.filterTimer);
+ this.filterTimer = null;
var url = this.filterUrl(form);
this.applyFilterUrl(url, push);
},
diff --git a/adminx/modules/Catalog/Controller.php b/adminx/modules/Catalog/Controller.php
index 7947128..ed72e6b 100644
--- a/adminx/modules/Catalog/Controller.php
+++ b/adminx/modules/Catalog/Controller.php
@@ -28,6 +28,7 @@
use App\Helpers\Request;
use App\Adminx\Rubrics\FieldAdminEditors;
use App\Adminx\Documents\Model as DocumentsModel;
+ use App\Adminx\Support\BulkActionExecutor;
use App\Adminx\Support\CodeEditor;
use App\Adminx\Support\SavedViews;
use App\Content\PaymentPrograms;
@@ -42,11 +43,15 @@
use App\Adminx\Packages\Products\FilterTemplateRevisions;
use App\Adminx\Packages\Products\FilterTemplates;
use App\Adminx\Packages\Products\QuickEditor;
+ use App\Adminx\Packages\Products\QuickEditorColumns;
+ use App\Adminx\Packages\Products\QuickEditorProfiles;
use App\Adminx\Packages\Products\ProductRelations;
use App\Adminx\Packages\Products\ProductCollections;
use App\Adminx\Packages\Products\ProductDemands;
+ use App\Adminx\Packages\Products\ProductReadiness;
use App\Adminx\Packages\Products\ShippingPackageTemplates;
use App\Adminx\Packages\Products\VariantGroups;
+ use App\Adminx\Packages\Commerce\Promotions;
use App\Modules\Products\VariantSelectorSettings;
use App\Modules\Products\ComparisonSettings;
use App\Modules\Products\DemandService;
@@ -74,7 +79,7 @@
public function products(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
if (!Model::hasCommerceCatalogs()) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Товарные каталоги не настроены'), 404); }
$this->assets();
$filters = array(
@@ -90,13 +95,13 @@
'stats' => Model::productStats(),
'filters' => $filters,
'saved_views' => SavedViews::all('catalog_products', Auth::id(), $this->savedViewFields('products')),
- 'can_manage' => Permission::check('manage_catalog'),
+ 'can_manage' => Permission::check('manage_products'),
));
}
public function productQuality(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
if (!Model::hasCommerceCatalogs()) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Товарные каталоги не настроены'), 404); }
$this->assets();
$stats = Model::productStats();
@@ -120,25 +125,25 @@
'issues' => $issues,
'filters' => $filters,
'saved_views' => SavedViews::all('catalog_quality', Auth::id(), $this->savedViewFields('quality')),
- 'can_manage' => Permission::check('manage_catalog'),
+ 'can_manage' => Permission::check('manage_products'),
));
}
public function productComparison(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
if (!Model::hasCommerceCatalogs()) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Товарные каталоги не настроены'), 404); }
$this->assets();
return $this->render('@products/product-comparison.twig', array(
'settings' => ComparisonSettings::get(),
'templates' => \App\Adminx\Rubrics\Model::templateOptions(),
- 'can_manage' => Permission::check('manage_catalog'),
+ 'can_manage' => Permission::check('manage_products'),
));
}
public function saveProductComparison(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try {
$settings = ComparisonSettings::save(Request::postAll());
} catch (\InvalidArgumentException $e) {
@@ -154,7 +159,7 @@
public function productDemands(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
if (!Model::hasCommerceCatalogs()) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Товарные каталоги не настроены'), 404); }
$this->assets();
$type = Request::getStr('type', '');
@@ -174,13 +179,13 @@
'filters' => $filters,
'settings' => DemandSettings::get(),
'templates' => \App\Adminx\Rubrics\Model::templateOptions(),
- 'can_manage' => Permission::check('manage_catalog'),
+ 'can_manage' => Permission::check('manage_products'),
));
}
public function saveProductDemandSettings(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try {
$settings = DemandSettings::save(Request::postAll());
} catch (\InvalidArgumentException $e) {
@@ -196,7 +201,7 @@
public function saveProductDemandStatus(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try {
$item = ProductDemands::setStatus(
isset($params['id']) ? (int) $params['id'] : 0,
@@ -212,7 +217,7 @@
public function notifyProductDemands(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
$result = DemandService::notifyReady($id > 0 ? 1 : 100, $id);
$this->audit('catalog.product_demand_notifications_sent', $id, $result);
@@ -224,7 +229,7 @@
public function deleteProductDemand(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try {
$item = ProductDemands::delete(isset($params['id']) ? (int) $params['id'] : 0);
} catch (\InvalidArgumentException $e) {
@@ -272,7 +277,7 @@
public function shipping(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
if (!Model::hasCommerceCatalogs()) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Товарные каталоги не настроены'), 404); }
$this->shippingAssets();
$state = Request::getStr('shipping_state', '');
@@ -292,13 +297,13 @@
'stats' => Model::productShippingStats(),
'filters' => $filters,
'package_templates' => ShippingPackageTemplates::all(),
- 'can_manage' => Permission::check('manage_catalog'),
+ 'can_manage' => Permission::check('manage_products'),
));
}
public function productCollections(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
if (!Model::hasCommerceCatalogs()) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Товарные каталоги не настроены'), 404); }
$this->collectionAssets();
return $this->render('@products/product-collections.twig', array(
@@ -309,13 +314,13 @@
'sort_modes' => ProductCollections::sortModes(),
'card_templates' => CardTemplates::options(),
'collections_available' => ProductCollections::available(),
- 'can_manage' => Permission::check('manage_catalog'),
+ 'can_manage' => Permission::check('manage_products'),
));
}
public function productCollection(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); }
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
$item = ProductCollections::one(isset($params['id']) ? (int) $params['id'] : 0);
return $item ? $this->success('', array('data' => array('collection' => $item)))
: $this->error('Подборка не найдена', array(), 404);
@@ -323,7 +328,7 @@
public function productCollectionProducts(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); }
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
return $this->success('', array('data' => array(
'items' => ProductCollections::productOptions(Request::getStr('q', ''), 30),
)));
@@ -331,7 +336,7 @@
public function saveProductCollection(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
try {
$item = ProductCollections::save($id, Request::postAll());
@@ -352,7 +357,7 @@
public function deleteProductCollection(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
if (!ProductCollections::delete($id)) {
return $this->error('Подборка не найдена', array(), 404);
@@ -364,26 +369,26 @@
public function productRelations(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
if (!Model::hasCommerceCatalogs()) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Товарные каталоги не настроены'), 404); }
$this->relationAssets();
$type = Request::getStr('type', '');
return $this->render('@products/product-relations.twig', array(
'relations' => ProductRelations::relations($type), 'bundles' => ProductRelations::bundles(),
'types' => ProductRelations::types(), 'stats' => ProductRelations::stats(), 'selected_type' => $type,
- 'can_manage' => Permission::check('manage_catalog'),
+ 'can_manage' => Permission::check('manage_products'),
));
}
public function productRelationProducts(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); }
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
return $this->success('', array('data' => array('items' => ProductRelations::productOptions(Request::getStr('q', ''), 40))));
}
public function saveProductRelation(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
try { $relation = ProductRelations::saveRelation($id, Request::postAll()); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
@@ -393,7 +398,7 @@
public function deleteProductRelation(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
if (!ProductRelations::deleteRelation($id)) { return $this->error('Связь не найдена', array(), 404); }
$this->audit('catalog.product_relation_deleted', $id);
@@ -402,14 +407,14 @@
public function productBundle(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); }
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
$bundle = ProductRelations::bundle(isset($params['id']) ? (int) $params['id'] : 0);
return $bundle ? $this->success('', array('data' => array('bundle' => $bundle))) : $this->error('Комплект не найден', array(), 404);
}
public function saveProductBundle(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
$items = Request::postJsonArray('items');
if (!is_array($items)) { return $this->error('Некорректный состав комплекта', array(), 422); }
@@ -421,7 +426,7 @@
public function deleteProductBundle(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
if (!ProductRelations::deleteBundle($id)) { return $this->error('Комплект не найден', array(), 404); }
$this->audit('catalog.product_bundle_deleted', $id);
@@ -430,7 +435,7 @@
public function productShipping(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); }
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
$productId = isset($params['id']) ? (int) $params['id'] : 0;
$product = Model::product($productId);
if (!$product) { return $this->error('Товар не найден', array(), 404); }
@@ -443,19 +448,19 @@
public function quickEdit(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
if (!Model::hasCommerceCatalogs()) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Товарные каталоги не настроены'), 404); }
$this->assets();
AdminAssets::addStyle('/modules/products/admin/assets/quick-edit.css', 53);
AdminAssets::addScript('/modules/products/admin/assets/quick-edit.js', 52);
$data = $this->quickEditData();
- if ($this->wantsPartial()) { return $this->partial('@catalog/partials/quick-edit-table.twig', $data); }
+ if ($this->wantsPartial()) { return $this->partial('@products/quick-edit-table.twig', $data); }
return $this->render('@products/quick-edit.twig', $data);
}
public function cardTemplates(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
$this->cardTemplateAssets();
return $this->render('@products/card-templates.twig', array(
'templates' => CardTemplates::all(Request::getStr('q', '')),
@@ -466,20 +471,20 @@
'filters' => array('q' => Request::getStr('q', '')),
'products' => CardTemplates::productOptions(100),
'default_markup' => CardTemplates::defaultMarkup(),
- 'can_manage' => Permission::check('manage_catalog'),
+ 'can_manage' => Permission::check('manage_products'),
));
}
public function cardTemplate(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); }
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
$item = CardTemplates::one(isset($params['id']) ? (int) $params['id'] : 0);
return $item ? $this->success('', array('data' => $item)) : $this->error('Представление не найдено', array(), 404);
}
public function saveCardContext(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$code = isset($params['code']) ? (string) $params['code'] : '';
try { $context = CardContexts::save($code, Request::postAll(), Auth::id()); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
@@ -493,7 +498,7 @@
public function filterTemplates(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
$this->cardTemplateAssets();
return $this->render('@products/filter-templates.twig', array(
'templates' => FilterTemplates::all(Request::getStr('q', '')),
@@ -501,13 +506,13 @@
'filters' => array('q' => Request::getStr('q', '')),
'catalogs' => FilterTemplates::previewOptions(100),
'default_markup' => FilterTemplates::defaultMarkup(),
- 'can_manage' => Permission::check('manage_catalog'),
+ 'can_manage' => Permission::check('manage_products'),
));
}
public function filterTemplate(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); }
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
$item = FilterTemplates::one(isset($params['id']) ? (int) $params['id'] : 0);
return $item ? $this->success('', array('data' => $item)) : $this->error('Представление не найдено', array(), 404);
}
@@ -517,7 +522,7 @@
public function lintFilterTemplate(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$result = CardTemplateSyntax::check(Request::postStr('draft_markup', ''));
return $result['ok'] ? $this->success($result['message'], array('data' => $result))
: $this->error($result['message'], array('draft_markup' => $result['message']), 422);
@@ -525,7 +530,7 @@
public function previewFilterTemplate(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$markup = Request::postStr('draft_markup', ''); $css = Request::postStr('draft_css', '');
$syntax = CardTemplateSyntax::check($markup);
if (!$syntax['ok']) { return $this->error($syntax['message'], array('draft_markup' => $syntax['message']), 422); }
@@ -548,7 +553,7 @@
public function publishFilterTemplate(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; } $id = isset($params['id']) ? (int) $params['id'] : 0;
+ if (($error = $this->productGuard()) !== null) { return $error; } $id = isset($params['id']) ? (int) $params['id'] : 0;
try { $item = FilterTemplates::publish($id, Auth::id()); } catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
$this->audit('catalog.filter_template_published', $id, array('version' => $item['version']));
return $this->success('Фильтры опубликованы', array('data' => $item));
@@ -556,14 +561,14 @@
public function copyFilterTemplate(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try { $id = FilterTemplates::copy(isset($params['id']) ? (int) $params['id'] : 0, Auth::id()); } catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
$this->audit('catalog.filter_template_copied', $id); return $this->success('Копия создана', array('data' => array('id' => $id)));
}
public function deleteFilterTemplate(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; } $id = isset($params['id']) ? (int) $params['id'] : 0;
+ if (($error = $this->productGuard()) !== null) { return $error; } $id = isset($params['id']) ? (int) $params['id'] : 0;
try { $deleted = FilterTemplates::delete($id); } catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
if (!$deleted) { return $this->error('Представление не найдено', array(), 404); }
$this->audit('catalog.filter_template_deleted', $id); return $this->success('Представление удалено');
@@ -571,33 +576,33 @@
public function filterTemplateRevisions(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); } $id = isset($params['id']) ? (int) $params['id'] : 0;
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); } $id = isset($params['id']) ? (int) $params['id'] : 0;
$item = FilterTemplates::one($id); if (!$item) { return $this->error('Представление не найдено', array(), 404); }
return $this->success('', array('data' => array('template' => array('id'=>$id,'title'=>$item['title']), 'revisions'=>FilterTemplateRevisions::listFor($id))));
}
public function filterTemplateRevision(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); }
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
$item=FilterTemplateRevisions::one(isset($params['revision'])?(int)$params['revision']:0);return $item?$this->success('',array('data'=>$item)):$this->error('Ревизия не найдена',array(),404);
}
public function restoreFilterTemplateRevision(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try{$id=FilterTemplateRevisions::restore(isset($params['revision'])?(int)$params['revision']:0,Auth::id());}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}
$this->audit('catalog.filter_template_revision_restored',$id);return $this->success('Ревизия восстановлена в черновик',array('data'=>array('id'=>$id)));
}
public function deleteFilterTemplateRevision(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }$id=FilterTemplateRevisions::delete(isset($params['revision'])?(int)$params['revision']:0);
+ if (($error = $this->productGuard()) !== null) { return $error; }$id=FilterTemplateRevisions::delete(isset($params['revision'])?(int)$params['revision']:0);
return $id?$this->success('Ревизия удалена',array('data'=>array('id'=>$id))):$this->error('Ревизия не найдена',array(),404);
}
public function deleteFilterTemplateRevisions(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }$id=isset($params['id'])?(int)$params['id']:0;
+ if (($error = $this->productGuard()) !== null) { return $error; }$id=isset($params['id'])?(int)$params['id']:0;
if(!FilterTemplates::one($id)){return $this->error('Представление не найдено',array(),404);}
return $this->success('Ревизии удалены',array('data'=>array('count'=>FilterTemplateRevisions::deleteFor($id))));
}
@@ -614,7 +619,7 @@
public function lintCardTemplate(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$result = CardTemplateSyntax::check(Request::postStr('draft_markup', ''));
return $result['ok']
? $this->success($result['message'], array('data' => $result))
@@ -623,7 +628,7 @@
public function previewCardTemplate(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$markup = Request::postStr('draft_markup', '');
$css = Request::postStr('draft_css', '');
$syntax = CardTemplateSyntax::check($markup);
@@ -651,7 +656,7 @@
public function publishCardTemplate(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
try { $item = CardTemplates::publish($id, Auth::id()); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
@@ -661,7 +666,7 @@
public function copyCardTemplate(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try { $id = CardTemplates::copy(isset($params['id']) ? (int) $params['id'] : 0, Auth::id()); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
$this->audit('catalog.card_template_copied', $id);
@@ -670,7 +675,7 @@
public function deleteCardTemplate(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
try { $deleted = CardTemplates::delete($id); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
@@ -681,7 +686,7 @@
public function cardTemplateRevisions(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); }
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
$id = isset($params['id']) ? (int) $params['id'] : 0;
$item = CardTemplates::one($id);
if (!$item) { return $this->error('Представление не найдено', array(), 404); }
@@ -690,14 +695,14 @@
public function cardTemplateRevision(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); }
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
$item = CardTemplateRevisions::one(isset($params['revision']) ? (int) $params['revision'] : 0);
return $item ? $this->success('', array('data' => $item)) : $this->error('Ревизия не найдена', array(), 404);
}
public function restoreCardTemplateRevision(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try { $id = CardTemplateRevisions::restore(isset($params['revision']) ? (int) $params['revision'] : 0, Auth::id()); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
$this->audit('catalog.card_template_revision_restored', $id);
@@ -706,14 +711,14 @@
public function deleteCardTemplateRevision(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = CardTemplateRevisions::delete(isset($params['revision']) ? (int) $params['revision'] : 0);
return $id ? $this->success('Ревизия удалена', array('data' => array('id' => $id))) : $this->error('Ревизия не найдена', array(), 404);
}
public function deleteCardTemplateRevisions(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
if (!CardTemplates::one($id)) { return $this->error('Представление не найдено', array(), 404); }
return $this->success('Ревизии удалены', array('data' => array('count' => CardTemplateRevisions::deleteFor($id))));
@@ -721,24 +726,26 @@
public function attributes(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
$this->attributeAssets();
- $view = Request::getStr('view', 'attributes');
- if (!in_array($view, array('normalization', 'attributes', 'sets', 'migration', 'sections'), true)) { $view = 'attributes'; }
+ $view = Request::getStr('view', 'overview');
+ if (!in_array($view, array('overview', 'normalization', 'attributes', 'sets', 'migration', 'sections'), true)) { $view = 'overview'; }
return $this->render('@products/attributes.twig', array(
'view' => $view, 'stats' => Attributes::stats(), 'value_types' => Attributes::valueTypes(),
'attributes' => Attributes::all(Request::getStr('q', ''), Request::getStr('status', '')),
+ 'product_attributes' => $view === 'migration' ? Attributes::all('', 'active', 'product') : array(),
'sets' => Attributes::sets(), 'legacy_fields' => $view === 'migration' ? Attributes::legacyFields() : array(),
'sections' => $view === 'sections' ? Attributes::sections() : array(),
'normalization' => $view === 'normalization' ? AttributeNormalization::audit() : array(),
+ 'overview' => $view === 'overview' ? Attributes::overview() : array(),
'filters' => array('q'=>Request::getStr('q',''),'status'=>Request::getStr('status','')),
- 'can_manage' => Permission::check('manage_catalog'),
+ 'can_manage' => Permission::check('manage_products'),
));
}
public function prepareAttributeNormalization(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try { $run = AttributeNormalization::prepare(Auth::id()); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
$this->audit('catalog.attribute_normalization_prepared', (int) $run['id'], $run['summary']);
@@ -750,7 +757,7 @@
public function approveAttributeNormalization(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
try { $run = AttributeNormalization::approve($id, Auth::id()); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
@@ -763,14 +770,14 @@
public function attributeNormalizationReport(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); }
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
$run = AttributeNormalization::run(isset($params['id']) ? (int) $params['id'] : 0);
return $run ? $this->success('', array('data' => $run)) : $this->error('Прогон не найден', array(), 404);
}
public function saveAttribute(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
try { $id = Attributes::save($id, Request::postAll(), Auth::id()); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
@@ -780,12 +787,12 @@
public function attribute(array $params = array())
{
- if(!Permission::check('view_catalog')){return $this->error('Недостаточно прав',array(),403);}$item=Attributes::one(isset($params['id'])?(int)$params['id']:0);return $item?$this->success('',array('data'=>$item)):$this->error('Характеристика не найдена',array(),404);
+ if(!Permission::check('view_products')){return $this->error('Недостаточно прав',array(),403);}$item=Attributes::one(isset($params['id'])?(int)$params['id']:0);return $item?$this->success('',array('data'=>$item)):$this->error('Характеристика не найдена',array(),404);
}
public function deleteAttribute(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try { Attributes::delete(isset($params['id']) ? (int) $params['id'] : 0); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
$this->audit('catalog.attribute_deleted', (int) $params['id']);
@@ -794,16 +801,16 @@
public function attributeSet(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
$set = Attributes::set(isset($params['id']) ? (int) $params['id'] : 0);
if (!$set) { return $this->renderStatus('@adminx/404.twig', array('title'=>'Набор не найден'), 404); }
$this->attributeAssets();
- return $this->render('@products/attribute-set.twig', array('set'=>$set,'attributes'=>Attributes::all('', 'active'),'can_manage'=>Permission::check('manage_catalog')));
+ return $this->render('@products/attribute-set.twig', array('set'=>$set,'attributes'=>Attributes::all('', 'active', 'product'),'can_manage'=>Permission::check('manage_products')));
}
public function saveAttributeSet(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id=isset($params['id'])?(int)$params['id']:0;
try{$id=Attributes::saveSet($id,Request::postAll(),Auth::id());}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}
$this->audit('catalog.attribute_set_saved',$id);return $this->success('Набор сохранён',array('redirect'=>$this->base().'/catalog/attribute-sets/'.$id,'data'=>array('id'=>$id)));
@@ -811,67 +818,67 @@
public function deleteAttributeSet(array $params = array())
{
- if(($error=$this->guard())!==null){return $error;}try{Attributes::deleteSet((int)$params['id']);}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}$this->audit('catalog.attribute_set_deleted',(int)$params['id']);return $this->success('Набор удалён',array('redirect'=>$this->base().'/catalog/attributes?view=sets'));
+ if(($error=$this->productGuard())!==null){return $error;}try{Attributes::deleteSet((int)$params['id']);}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}$this->audit('catalog.attribute_set_deleted',(int)$params['id']);return $this->success('Набор удалён',array('redirect'=>$this->base().'/catalog/attributes?view=sets'));
}
public function saveAttributeGroup(array $params = array())
{
- if(($error=$this->guard())!==null){return $error;}try{$id=Attributes::saveGroup((int)$params['id'],Request::postInt('group_id',0),Request::postStr('name',''));}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}return $this->success('Группа сохранена',array('data'=>array('id'=>$id)));
+ if(($error=$this->productGuard())!==null){return $error;}try{$id=Attributes::saveGroup((int)$params['id'],Request::postInt('group_id',0),Request::postStr('name',''));}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}return $this->success('Группа сохранена',array('data'=>array('id'=>$id)));
}
public function deleteAttributeGroup(array $params = array())
{
- if(($error=$this->guard())!==null){return $error;}Attributes::deleteGroup((int)$params['id'],(int)$params['group']);return $this->success('Группа удалена, характеристики перенесены в «Без группы»');
+ if(($error=$this->productGuard())!==null){return $error;}Attributes::deleteGroup((int)$params['id'],(int)$params['group']);return $this->success('Группа удалена, характеристики перенесены в «Без группы»');
}
public function saveAttributeSetItem(array $params = array())
{
- if(($error=$this->guard())!==null){return $error;}try{$id=Attributes::saveSetItem((int)$params['id'],Request::postAll());}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}return $this->success('Характеристика добавлена',array('data'=>array('id'=>$id)));
+ if(($error=$this->productGuard())!==null){return $error;}try{$id=Attributes::saveSetItem((int)$params['id'],Request::postAll());}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}return $this->success('Характеристика добавлена',array('data'=>array('id'=>$id)));
}
public function deleteAttributeSetItem(array $params = array())
{
- if(($error=$this->guard())!==null){return $error;}Attributes::deleteSetItem((int)$params['id'],(int)$params['attribute']);return $this->success('Характеристика удалена из набора');
+ if(($error=$this->productGuard())!==null){return $error;}Attributes::deleteSetItem((int)$params['id'],(int)$params['attribute']);return $this->success('Характеристика удалена из набора');
}
public function saveAttributeMapping(array $params = array())
{
- if(($error=$this->guard())!==null){return $error;}try{$data=Attributes::saveLegacyMap((int)$params['field'],Request::postAll(),Auth::id());}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}return $this->success('Сопоставление сохранено',array('data'=>$data));
+ if(($error=$this->productGuard())!==null){return $error;}try{$data=Attributes::saveLegacyMap((int)$params['field'],Request::postAll(),Auth::id());}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}return $this->success('Сопоставление сохранено',array('data'=>$data));
}
public function attributeMappingPreview(array $params = array())
{
- if(!Permission::check('view_catalog')){return $this->error('Недостаточно прав',array(),403);}try{$data=Attributes::migrationPreview((int)$params['field']);}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}return $this->success('',array('data'=>$data));
+ if(!Permission::check('view_products')){return $this->error('Недостаточно прав',array(),403);}try{$data=Attributes::migrationPreview((int)$params['field']);}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}return $this->success('',array('data'=>$data));
}
public function migrateAttributeField(array $params = array())
{
- if(($error=$this->guard())!==null){return $error;}try{$data=Attributes::migrate((int)$params['field'],Auth::id(),Request::postBool('verified',false));}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}$this->audit('catalog.attribute_field_migrated',(int)$params['field'],$data);return $this->success('Значения перенесены в новый контур',array('data'=>$data));
+ if(($error=$this->productGuard())!==null){return $error;}try{$data=Attributes::migrate((int)$params['field'],Auth::id(),Request::postBool('verified',false));}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}$this->audit('catalog.attribute_field_migrated',(int)$params['field'],$data);return $this->success('Значения перенесены в новый контур',array('data'=>$data));
}
public function verifyAttributeField(array $params = array())
{
- if(($error=$this->guard())!==null){return $error;}try{$data=Attributes::verifyMigration((int)$params['field'],Auth::id());}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}$this->audit('catalog.attribute_field_verified',(int)$params['field'],$data);return $this->success('Перенесённые значения подтверждены',array('data'=>$data));
+ if(($error=$this->productGuard())!==null){return $error;}try{$data=Attributes::verifyMigration((int)$params['field'],Auth::id());}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}$this->audit('catalog.attribute_field_verified',(int)$params['field'],$data);return $this->success('Перенесённые значения подтверждены',array('data'=>$data));
}
public function saveAttributeSection(array $params = array())
{
- if(($error=$this->guard())!==null){return $error;}try{$data=Attributes::saveSection((int)$params['id'],Request::postAll(),Auth::id());}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}return $this->success('Режим раздела сохранён',array('data'=>$data));
+ if(($error=$this->productGuard())!==null){return $error;}try{$data=Attributes::saveSection((int)$params['id'],Request::postAll(),Auth::id());}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}return $this->success('Режим раздела сохранён',array('data'=>$data));
}
public function rebuildAttributeShadow(array $params = array())
{
- if(($error=$this->guard())!==null){return $error;}try{$data=Attributes::rebuildShadowIndex((int)$params['id']);}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}$this->audit('catalog.attribute_shadow_rebuilt',(int)$params['id'],$data);return $this->success('Теневой индекс раздела перестроен',array('data'=>$data));
+ if(($error=$this->productGuard())!==null){return $error;}try{$data=Attributes::rebuildShadowIndex((int)$params['id']);}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}$this->audit('catalog.attribute_shadow_rebuilt',(int)$params['id'],$data);return $this->success('Теневой индекс раздела перестроен',array('data'=>$data));
}
public function attributeShadowReport(array $params = array())
{
- if(!Permission::check('view_catalog')){return $this->error('Недостаточно прав',array(),403);}try{$data=Attributes::shadowReport((int)$params['id']);}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}return $this->success('',array('data'=>$data));
+ if(!Permission::check('view_products')){return $this->error('Недостаточно прав',array(),403);}try{$data=Attributes::shadowReport((int)$params['id']);}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}return $this->success('',array('data'=>$data));
}
public function saveQuickEdit(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$productId = isset($params['id']) ? (int) $params['id'] : 0;
$key = Request::postStr('key', '');
try { $saved = QuickEditor::save($productId, $key, Request::postStr('value', ''), Auth::id()); }
@@ -880,9 +887,48 @@
return $this->success('Изменение сохранено', array('data' => $saved));
}
+ public function saveQuickEditProfile(array $params = array())
+ {
+ if (($error = $this->productGuard()) !== null) { return $error; }
+ $columns = json_decode(Request::postStr('columns_json', '[]'), true);
+ if (!is_array($columns)) { return $this->error('Не удалось прочитать набор колонок', array(), 422); }
+ try {
+ $columns = QuickEditorColumns::normalize($columns);
+ $profile = QuickEditorProfiles::save(
+ Request::postInt('id', 0),
+ Auth::id(),
+ Request::postAll(),
+ $columns
+ );
+ } catch (\Throwable $e) {
+ return $this->error($e->getMessage(), array(), 422);
+ }
+
+ $this->audit('catalog.quick_edit_profile_saved', (int) $profile['id'], array(
+ 'title' => (string) $profile['title'],
+ 'columns' => count($columns),
+ 'shared' => (int) $profile['is_shared'],
+ ));
+ return $this->success('Набор колонок сохранён', array('data' => array('profile' => $profile)));
+ }
+
+ public function deleteQuickEditProfile(array $params = array())
+ {
+ if (($error = $this->productGuard()) !== null) { return $error; }
+ $id = isset($params['id']) ? (int) $params['id'] : 0;
+ if (!QuickEditorProfiles::delete($id, Auth::id())) {
+ return $this->error('Профиль не найден или принадлежит другому пользователю', array(), 404);
+ }
+
+ $this->audit('catalog.quick_edit_profile_deleted', $id);
+ return $this->success('Набор колонок удалён', array(
+ 'redirect' => $this->base() . '/catalog/quick-edit',
+ ));
+ }
+
public function setProductPaymentProgram(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$productId = isset($params['id']) ? (int) $params['id'] : 0;
if (!Model::product($productId)) { return $this->error('Товар не найден', array(), 404); }
try { $enabled = PaymentPrograms::setProduct($productId, isset($params['program']) ? $params['program'] : '', Request::postBool('enabled', false)); }
@@ -905,19 +951,19 @@
public function variantGroups(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
if (!Model::hasCommerceCatalogs()) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Товарные каталоги не настроены'), 404); }
$this->assets();
return $this->render('@products/variant-groups.twig', array(
'groups' => VariantGroups::all(),
'selector_settings' => VariantSelectorSettings::get(),
- 'can_manage' => Permission::check('manage_catalog'),
+ 'can_manage' => Permission::check('manage_products'),
));
}
public function saveVariantSelectorSettings(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$settings = VariantSelectorSettings::save(Request::postAll());
$this->audit('catalog.variant_selector_settings_saved', 0, $settings);
return $this->success('Вид выбора вариантов сохранён', array(
@@ -928,7 +974,7 @@
public function variantGroup(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
+ if (!Permission::check('view_products')) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403); }
$group = VariantGroups::one(isset($params['id']) ? (int) $params['id'] : 0);
if (!$group) { return $this->renderStatus('@adminx/404.twig', array('title' => 'Группа не найдена'), 404); }
$this->shippingAssets();
@@ -939,13 +985,13 @@
'variant_attributes' => VariantGroups::nativeAttributes(),
'package_templates' => ShippingPackageTemplates::all(),
'candidates' => $search !== '' ? Model::products(array('q' => $search, 'page' => 1)) : array('items' => array()),
- 'can_manage' => Permission::check('manage_catalog'),
+ 'can_manage' => Permission::check('manage_products'),
));
}
public function saveVariantGroup(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
try { $id = VariantGroups::save($id, Request::postStr('title', ''), Request::postBool('status', false), Auth::id()); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
@@ -955,7 +1001,7 @@
public function addVariantProduct(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try { VariantGroups::add((int) $params['id'], Request::postInt('product_id', 0)); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
$this->audit('catalog.variant_added', (int) $params['id'], array('product_id' => Request::postInt('product_id', 0)));
@@ -964,7 +1010,7 @@
public function primaryVariantProduct(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try { VariantGroups::primary((int) $params['id'], (int) $params['product']); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
$this->audit('catalog.variant_primary_changed', (int) $params['id'], array('product_id' => (int) $params['product']));
@@ -973,7 +1019,7 @@
public function removeVariantProduct(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try { VariantGroups::remove((int) $params['id'], (int) $params['product']); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
$this->audit('catalog.variant_removed', (int) $params['id'], array('product_id' => (int) $params['product']));
@@ -982,7 +1028,7 @@
public function statusVariantProduct(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$status = Request::postBool('status', false);
try { VariantGroups::status((int) $params['id'], (int) $params['product'], $status); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
@@ -992,7 +1038,7 @@
public function saveVariantAttribute(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try { VariantGroups::saveAttribute((int) $params['id'], (int) $params['product'], Request::postInt('field_id', 0), Request::postStr('label', ''), Request::postStr('swatch', '')); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
$this->audit('catalog.variant_attribute_saved', (int) $params['id'], array('product_id' => (int) $params['product'], 'field_id' => Request::postInt('field_id', 0)));
@@ -1001,7 +1047,7 @@
public function deleteVariantAttribute(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
VariantGroups::deleteAttribute((int) $params['id'], (int) $params['product'], (int) $params['field']);
$this->audit('catalog.variant_attribute_deleted', (int) $params['id'], array('product_id' => (int) $params['product'], 'field_id' => (int) $params['field']));
return $this->success('Атрибут удалён');
@@ -1009,7 +1055,7 @@
public function saveVariantNativeAttribute(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$attributeId = Request::postInt('attribute_id', 0);
try {
VariantGroups::saveNativeAttribute(
@@ -1031,7 +1077,7 @@
public function deleteVariantNativeAttribute(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
VariantGroups::deleteNativeAttribute((int) $params['id'], (int) $params['product'], (int) $params['attribute']);
$this->audit('catalog.variant_native_attribute_deleted', (int) $params['id'], array(
'product_id' => (int) $params['product'],
@@ -1042,7 +1088,7 @@
public function reorderVariantProducts(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$order = Request::postJsonArray('order');
if (!is_array($order)) { return $this->error('Некорректный порядок', array(), 422); }
try { VariantGroups::reorder((int) $params['id'], $order); }
@@ -1053,7 +1099,7 @@
public function copyVariantProduct(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$sourceId = (int) $params['product'];
$newId = 0;
try {
@@ -1071,7 +1117,7 @@
public function deleteVariantGroup(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try { VariantGroups::delete((int) $params['id']); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
$this->audit('catalog.variant_group_deleted', (int) $params['id']);
@@ -1080,7 +1126,7 @@
public function reindexProducts(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try { $result = Model::reindexProducts(); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 500); }
$this->audit('catalog.products_reindexed', null, $result);
@@ -1089,7 +1135,7 @@
public function reindexProduct(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
if ($id <= 0 || !DocumentsModel::one($id)) { return $this->error('Товар не найден', array(), 404); }
try {
@@ -1105,7 +1151,7 @@
public function createProductVariantGroup(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$productId = isset($params['id']) ? (int) $params['id'] : 0;
$product = Model::product($productId);
if (!$product) { return $this->error('Товар не найден', array(), 404); }
@@ -1120,7 +1166,7 @@
public function editProduct(array $params = array())
{
- if (!Permission::check('manage_catalog') || !Permission::check('manage_documents')) {
+ if (!Permission::check('manage_products') || !Permission::check('manage_documents')) {
return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403);
}
@@ -1132,6 +1178,9 @@
AdminAssets::addScript($this->base() . '/modules/Documents/assets/documents.js', 50);
CodeEditor::useRichEditor();
$this->attributeAssets();
+ $variantGroup = VariantGroups::membership($id);
+ $shippingProfile = ProductShipping::profile($id);
+ $nativeAttributes = Attributes::productEditor($id);
return $this->render('@documents/edit.twig', array(
'document' => $document,
'field_groups' => DocumentsModel::fieldsForDocument($id),
@@ -1141,18 +1190,110 @@
'authors' => DocumentsModel::authors(),
'is_new' => false, 'can_manage' => true,
'actor_id' => Auth::id(),
- 'catalog_mode' => true, 'product' => $product, 'variant_group' => VariantGroups::membership($id),
- 'shipping_profile' => ProductShipping::profile($id),
+ 'catalog_mode' => true, 'product' => $product, 'variant_group' => $variantGroup,
+ 'shipping_profile' => $shippingProfile,
'package_templates' => ShippingPackageTemplates::all(),
- 'native_attributes' => Attributes::productEditor($id),
+ 'native_attributes' => $nativeAttributes,
+ 'product_readiness' => ProductReadiness::build($product, $nativeAttributes, $shippingProfile, $variantGroup),
+ 'product_promotions_available' => Promotions::available(),
+ 'product_promotions' => Promotions::forProduct($id),
+ 'can_manage_product_promotions' => Permission::check('manage_orders'),
'return_url' => $this->base() . '/catalog/products',
'submit_url' => $this->base() . '/documents/' . $id,
));
}
+ public function saveProductPromotion(array $params = array())
+ {
+ if (($error = $this->productPromotionGuard()) !== null) { return $error; }
+ $productId = isset($params['id']) ? (int) $params['id'] : 0;
+ $promotionId = isset($params['promotion']) ? (int) $params['promotion'] : 0;
+ $product = Model::product($productId);
+ if (!$product) { return $this->error('Товар не найден', array(), 404); }
+ if ($promotionId > 0 && !Promotions::isDirectProductRule($promotionId, $productId)) {
+ return $this->error('Эта акция управляется через общий конструктор', array(), 409);
+ }
+
+ $rewardType = Request::postStr('reward_type', 'percent');
+ $targetMode = Request::postStr('target_mode', 'current');
+ $targetId = $targetMode === 'current' && $rewardType !== 'gift'
+ ? $productId
+ : Request::postInt('target_product_id', 0);
+ $title = Request::postStr('title', '');
+ if ($title === '') { $title = 'Акция: ' . (string) $product['title']; }
+ $input = array(
+ 'title' => $title,
+ 'code' => Request::postStr('code', ''),
+ 'description' => Request::postStr('description', ''),
+ 'status' => Request::postBool('status', false),
+ 'condition_scope' => 'products',
+ 'condition_product_ids' => array($productId),
+ 'condition_quantity' => Request::postInt('condition_quantity', 1),
+ 'condition_min_total' => 0,
+ 'reward_type' => $rewardType,
+ 'reward_scope' => 'products',
+ 'reward_product_ids' => $rewardType === 'gift' ? array() : array($targetId),
+ 'reward_product_id' => $rewardType === 'gift' ? $targetId : 0,
+ 'reward_value' => Request::post('reward_value', 0),
+ 'reward_quantity' => Request::postInt('reward_quantity', 1),
+ 'max_uses_per_cart' => 0,
+ 'allow_coupon' => Request::postBool('allow_coupon', false),
+ 'stop_processing' => false,
+ 'starts_at' => Request::postStr('starts_at', ''),
+ 'ends_at' => Request::postStr('ends_at', ''),
+ 'priority' => Request::postInt('priority', 100),
+ );
+ try {
+ $promotionId = Promotions::save($promotionId, $input);
+ } catch (\Throwable $e) {
+ return $this->error($e->getMessage(), array(), 422);
+ }
+
+ $this->audit('catalog.product_promotion_saved', $productId, array('promotion_id' => $promotionId));
+ return $this->success('Акция товара сохранена', array('data' => array('id' => $promotionId), 'reload' => true));
+ }
+
+ public function toggleProductPromotion(array $params = array())
+ {
+ if (($error = $this->productPromotionGuard()) !== null) { return $error; }
+ $productId = isset($params['id']) ? (int) $params['id'] : 0;
+ $promotionId = isset($params['promotion']) ? (int) $params['promotion'] : 0;
+ if (!Promotions::isDirectProductRule($promotionId, $productId)) {
+ return $this->error('Эта акция управляется через общий конструктор', array(), 409);
+ }
+
+ try {
+ Promotions::toggle($promotionId, Request::postBool('active', false));
+ } catch (\Throwable $e) {
+ return $this->error($e->getMessage(), array(), 422);
+ }
+
+ $this->audit('catalog.product_promotion_toggled', $productId, array('promotion_id' => $promotionId));
+ return $this->success('Состояние акции обновлено');
+ }
+
+ public function deleteProductPromotion(array $params = array())
+ {
+ if (($error = $this->productPromotionGuard()) !== null) { return $error; }
+ $productId = isset($params['id']) ? (int) $params['id'] : 0;
+ $promotionId = isset($params['promotion']) ? (int) $params['promotion'] : 0;
+ if (!Promotions::isDirectProductRule($promotionId, $productId)) {
+ return $this->error('Эта акция управляется через общий конструктор', array(), 409);
+ }
+
+ try {
+ Promotions::delete($promotionId);
+ } catch (\Throwable $e) {
+ return $this->error($e->getMessage(), array(), 422);
+ }
+
+ $this->audit('catalog.product_promotion_deleted', $productId, array('promotion_id' => $promotionId));
+ return $this->success('Акция товара удалена', array('reload' => true));
+ }
+
public function saveProductShipping(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$productId = isset($params['id']) ? (int) $params['id'] : 0;
$packages = Request::postJsonArray('packages');
if (!is_array($packages)) { return $this->error('Некорректный список грузовых мест', array(), 422); }
@@ -1164,12 +1305,12 @@
public function saveProductAttributes(array $params = array())
{
- if(($error=$this->guard())!==null){return $error;}$productId=isset($params['id'])?(int)$params['id']:0;if(!Model::product($productId)){return $this->error('Товар не найден',array(),404);}try{$data=Attributes::saveProductValues($productId,Request::postAll(),Auth::id());}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}$this->audit('catalog.product_attributes_saved',$productId,$data);return $this->success('Характеристики товара сохранены',array('data'=>$data));
+ if(($error=$this->productGuard())!==null){return $error;}$productId=isset($params['id'])?(int)$params['id']:0;if(!Model::product($productId)){return $this->error('Товар не найден',array(),404);}try{$data=Attributes::saveProductValues($productId,Request::postAll(),Auth::id());}catch(\Throwable $e){return $this->error($e->getMessage(),array(),422);}$this->audit('catalog.product_attributes_saved',$productId,$data);return $this->success('Характеристики товара сохранены',array('data'=>$data));
}
public function copyProductShippingToVariants(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$productId = isset($params['id']) ? (int)$params['id'] : 0; $membership = VariantGroups::membership($productId);
if (!$membership) { return $this->error('Товар не входит в группу вариантов', array(), 422); }
$group = VariantGroups::one((int)$membership['id']); $targets = array();
@@ -1182,22 +1323,64 @@
public function bulkProductShipping(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$ids=Request::postJsonArray('ids');if(!is_array($ids)||count($ids)<1||count($ids)>200){return $this->error('Выберите от 1 до 200 товаров',array(),422);}
$enabled=Request::postBool('enabled',false);$done=ProductShipping::setEnabled($ids,$enabled);
$this->audit('catalog.product_shipping_bulk_status',null,array('enabled'=>$enabled,'updated'=>$done));
return $this->success(($enabled?'Расчёт включён: ':'Расчёт выключен: ').$done,array('data'=>array('updated'=>$done)));
}
+ public function bulkProducts(array $params = array())
+ {
+ if (($error = $this->productGuard()) !== null) { return $error; }
+ $authorId = Auth::id();
+ try {
+ $result = BulkActionExecutor::execute(
+ Request::postStr('action', ''),
+ Request::post('ids', array()),
+ array(
+ 'publish' => function ($id) use ($authorId) {
+ QuickEditor::save($id, 'active', '1', $authorId);
+ return true;
+ },
+ 'disable' => function ($id) use ($authorId) {
+ QuickEditor::save($id, 'active', '0', $authorId);
+ return true;
+ },
+ 'show_price' => function ($id) use ($authorId) {
+ QuickEditor::save($id, 'show_price', '1', $authorId);
+ return true;
+ },
+ 'hide_price' => function ($id) use ($authorId) {
+ QuickEditor::save($id, 'show_price', '0', $authorId);
+ return true;
+ },
+ 'reindex' => function ($id) {
+ Model::reindexDocument($id);
+ return true;
+ },
+ ),
+ 300
+ );
+ } catch (\InvalidArgumentException $e) {
+ return $this->error($e->getMessage(), array(), 422);
+ }
+
+ $this->audit('catalog.products_bulk_action', null, $result);
+ $message = 'Обработано товаров: ' . (int) $result['done'];
+ if (!empty($result['errors'])) { $message .= ', ошибок: ' . count($result['errors']); }
+ return $this->success($message, array('data' => $result));
+ }
+
public function shippingPackageTemplates(array $params = array())
{
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); }
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
return $this->success('', array('data' => array('templates' => ShippingPackageTemplates::all())));
}
public function saveShippingPackageTemplate(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
try { $template = ShippingPackageTemplates::save($id, Request::postAll()); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array(), 422); }
@@ -1210,7 +1393,7 @@
public function deleteShippingPackageTemplate(array $params = array())
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
$template = ShippingPackageTemplates::one($id);
if (!$template) { return $this->error('Шаблон упаковки не найден', array(), 404); }
@@ -1221,7 +1404,7 @@
public function createProduct(array $params = array())
{
- if (!Permission::check('manage_catalog') || !Permission::check('manage_documents')) {
+ if (!Permission::check('manage_products') || !Permission::check('manage_documents')) {
return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403);
}
@@ -1410,7 +1593,7 @@
protected function persistCardTemplate($id)
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try { $saved = CardTemplates::save((int) $id, Request::postAll(), Auth::id()); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array('draft_markup' => $e->getMessage()), 422); }
$this->audit($id > 0 ? 'catalog.card_template_saved' : 'catalog.card_template_created', $saved);
@@ -1419,7 +1602,7 @@
protected function persistFilterTemplate($id)
{
- if (($error = $this->guard()) !== null) { return $error; }
+ if (($error = $this->productGuard()) !== null) { return $error; }
try { $saved = FilterTemplates::save((int) $id, Request::postAll(), Auth::id()); }
catch (\Throwable $e) { return $this->error($e->getMessage(), array('draft_markup' => $e->getMessage()), 422); }
$this->audit($id > 0 ? 'catalog.filter_template_saved' : 'catalog.filter_template_created', $saved);
@@ -1445,10 +1628,25 @@
return $this->guardPermission('manage_catalog');
}
+ protected function productGuard()
+ {
+ return $this->guardPermission('manage_products');
+ }
+
+ protected function productPromotionGuard()
+ {
+ if (($error = $this->csrfGuard()) !== null) { return $error; }
+ if (!Permission::check('manage_products') || !Permission::check('manage_orders')) {
+ return $this->error('Недостаточно прав для управления акциями', array(), 403);
+ }
+
+ return null;
+ }
+
protected function savedViewGuard()
{
if (($error = $this->csrfGuard()) !== null) { return $error; }
- if (!Permission::check('view_catalog')) { return $this->error('Недостаточно прав', array(), 403); }
+ if (!Permission::check('view_products')) { return $this->error('Недостаточно прав', array(), 403); }
return null;
}
@@ -1475,13 +1673,21 @@
'q' => Request::getStr('q', ''), 'rubric_id' => Request::getInt('rubric_id', 0),
'category_id' => Request::getInt('category_id', 0), 'state' => Request::getStr('state', ''),
'incomplete' => Request::getBool('incomplete', false), 'limit' => Request::getInt('limit', 25),
- 'page' => Request::getInt('page', 1),
+ 'page' => Request::getInt('page', 1), 'profile' => Request::getStr('profile', ''),
);
$filters = Hooks::filter('catalog.quick_edit.filters', $filters);
if (!is_array($filters)) { $filters = array(); }
- return array('result' => QuickEditor::items($filters), 'filters' => $filters,
+ $profiles = QuickEditorProfiles::all(Auth::id());
+ $activeProfile = QuickEditorProfiles::active(isset($filters['profile']) ? $filters['profile'] : '', Auth::id());
+ $columns = QuickEditorColumns::normalize(isset($activeProfile['columns']) ? $activeProfile['columns'] : array());
+ $filters['profile'] = (string) $activeProfile['id'];
+ return array('result' => QuickEditor::items($filters, $columns), 'filters' => $filters,
'rubrics' => QuickEditor::rubrics(), 'categories' => QuickEditor::categories(),
- 'can_manage' => Permission::check('manage_catalog'));
+ 'can_manage' => Permission::check('manage_products'),
+ 'quick_profiles' => $profiles,
+ 'quick_profile' => array_merge($activeProfile, array('columns' => $columns)),
+ 'quick_column_groups' => QuickEditorColumns::groups(),
+ 'quick_profiles_available' => QuickEditorProfiles::available());
}
protected function assets()
diff --git a/adminx/modules/Catalog/assets/catalog-drawer.css b/adminx/modules/Catalog/assets/catalog-drawer.css
index 248f88c..996216d 100644
--- a/adminx/modules/Catalog/assets/catalog-drawer.css
+++ b/adminx/modules/Catalog/assets/catalog-drawer.css
@@ -217,7 +217,6 @@
gap: 12px;
flex: 0 0 auto;
font-size: 11px;
- font-variant-numeric: tabular-nums;
}
.catalog-field-groups {
display: grid;
@@ -299,7 +298,6 @@
flex: 0 0 auto;
color: var(--text-secondary);
font-size: 11px;
- font-variant-numeric: tabular-nums;
}
.catalog-field-group-meta .badge {
min-width: 88px;
@@ -459,7 +457,6 @@
margin-bottom: 10px;
color: var(--text-secondary);
font-size: 13px;
- font-variant-numeric: tabular-nums;
}
.catalog-recompile-errors {
margin-top: 14px;
diff --git a/adminx/modules/Catalog/assets/catalog.css b/adminx/modules/Catalog/assets/catalog.css
index 6f5e739..76d8e86 100644
--- a/adminx/modules/Catalog/assets/catalog.css
+++ b/adminx/modules/Catalog/assets/catalog.css
@@ -1,4 +1,4 @@
-.catalog-summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px;margin-bottom:16px}.catalog-stat{display:flex;align-items:center;gap:14px;padding:16px}.catalog-stat b{display:block;font-size:20px;line-height:1.1;font-weight:800;font-variant-numeric:tabular-nums}.catalog-stat span{font-size:13px;color:var(--text-secondary)}.catalog-panel-header{margin-bottom:18px}.catalog-panel-header h2{font-size:22px;line-height:1.2;font-weight:800;letter-spacing:0}.catalog-card{padding:0;overflow:hidden}.catalog-section-head{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:18px 20px;border-bottom:1px solid var(--border-default)}.catalog-section-title{display:flex;align-items:flex-start;gap:12px;min-width:0}.catalog-section-title h2{margin:0 0 4px;font-size:16px;line-height:1.25;font-weight:700;letter-spacing:0}.catalog-section-title p{margin:0;font-size:13px}.catalog-head-icon{width:34px;height:34px;border-radius:var(--radius-md);font-size:17px}.catalog-table{width:100%;table-layout:fixed}.catalog-table td b{display:block}.catalog-meta{display:block;margin-top:3px;color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.catalog-actions-col{width:140px}.catalog-table a{text-decoration:none}.catalog-page-tabs{margin-bottom:18px;overflow-x:auto}.catalog-page-tabs .tab{text-decoration:none;white-space:nowrap}.catalog-tree-search{width:min(320px,40vw)}.catalog-tree,.catalog-tree-children{margin:0;padding:0;list-style:none}.catalog-tree{padding:8px 0}.catalog-tree-children{margin-left:34px;border-left:1px solid var(--border-default)}.catalog-tree-row{display:grid;grid-template-columns:30px 22px minmax(180px,1fr) 80px 70px 84px;align-items:center;gap:8px;min-height:54px;padding:7px 16px;border-bottom:1px solid var(--border-subtle,var(--border-default))}.catalog-tree-item:last-child>.catalog-tree-row{border-bottom:0}.catalog-tree-item.is-filtered{display:none}.catalog-tree-item.is-dragging>.catalog-tree-row{opacity:.55;background:var(--blue-50)}.catalog-drag{display:grid;place-items:center;width:30px;height:30px;padding:0;border:0;background:transparent;color:var(--text-tertiary);cursor:grab}.catalog-drag:active{cursor:grabbing}.catalog-tree-branch{color:var(--text-tertiary)}.catalog-tree-name{min-width:0}.catalog-tree-name b,.catalog-tree-name small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.catalog-tree-name b{font-size:13px}.catalog-tree-name small{font-size:11px;color:var(--text-secondary)}.catalog-tree-count{text-align:center;font-size:12px;font-variant-numeric:tabular-nums;color:var(--text-secondary)}.catalog-tree-actions{display:flex;justify-content:flex-end;gap:4px}.catalog-action-edit{color:var(--blue-600)}.catalog-action-delete{color:var(--red-600)}.catalog-card-body{padding:18px 20px}.catalog-settings-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;padding:0 20px 20px}.catalog-settings-grid>section{min-width:0}.catalog-settings-grid h3{margin:0 0 4px;font-size:14px}.catalog-check-list,.catalog-filter-list{display:grid;gap:6px;max-height:440px;margin-top:12px;overflow-y:auto}.catalog-check-list label,.catalog-filter-row{min-width:0;padding:9px 10px;border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--background-card)}.catalog-check-list label{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:9px;align-items:center}.catalog-check-list small{grid-column:2;color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.catalog-filter-row{display:grid;grid-template-columns:minmax(0,1fr) minmax(160px,210px);align-items:center;gap:10px}.catalog-filter-row label{display:flex;align-items:center;gap:9px;min-width:0}.catalog-filter-row label span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.catalog-form-footer{display:flex;justify-content:flex-end;padding:14px 20px;border-top:1px solid var(--border-default)}.catalog-drawer{width:min(1000px,66.666vw);max-width:none}.catalog-item-form{display:block;overflow-x:hidden}.catalog-drawer-tabs{position:sticky;top:0;z-index:2;margin:-20px -20px 18px;padding:12px 20px;border-bottom:1px solid var(--border-default);background:var(--background-card)}.catalog-drawer-tabs .tab{text-decoration:none}.catalog-item-form [data-catalog-drawer-panel]{min-width:0}@media(max-width:900px){.catalog-settings-grid{grid-template-columns:1fr}.catalog-drawer{width:min(100%,720px)}.catalog-tree-row{grid-template-columns:30px 18px minmax(140px,1fr) 70px 70px}.catalog-tree-count{display:none}}@media(max-width:640px){.catalog-section-head{align-items:stretch;flex-direction:column}.catalog-tree-search{width:100%}.catalog-tree-row{grid-template-columns:28px minmax(120px,1fr) 70px 76px}.catalog-tree-branch{display:none}.catalog-tree-children{margin-left:16px}.catalog-settings-grid{padding:0 14px 14px}.catalog-filter-row{grid-template-columns:1fr}.catalog-table th:nth-child(3),.catalog-table td:nth-child(3){display:none}}
+.catalog-summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px;margin-bottom:16px}.catalog-stat{display:flex;align-items:center;gap:14px;padding:16px}.catalog-stat b{display:block;font-size:20px;line-height:1.1;font-weight:800;}.catalog-stat span{font-size:13px;color:var(--text-secondary)}.catalog-panel-header{margin-bottom:18px}.catalog-panel-header h2{font-size:22px;line-height:1.2;font-weight:800;letter-spacing:0}.catalog-card{padding:0;overflow:hidden}.catalog-section-head{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:18px 20px;border-bottom:1px solid var(--border-default)}.catalog-section-title{display:flex;align-items:flex-start;gap:12px;min-width:0}.catalog-section-title h2{margin:0 0 4px;font-size:16px;line-height:1.25;font-weight:700;letter-spacing:0}.catalog-section-title p{margin:0;font-size:13px}.catalog-head-icon{width:34px;height:34px;border-radius:var(--radius-md);font-size:17px}.catalog-table{width:100%;table-layout:fixed}.catalog-table td b{display:block}.catalog-meta{display:block;margin-top:3px;color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.catalog-actions-col{width:140px}.catalog-table a{text-decoration:none}.catalog-page-tabs{margin-bottom:18px;overflow-x:auto}.catalog-page-tabs .tab{text-decoration:none;white-space:nowrap}.catalog-tree-search{width:min(320px,40vw)}.catalog-tree,.catalog-tree-children{margin:0;padding:0;list-style:none}.catalog-tree{padding:8px 0}.catalog-tree-children{margin-left:34px;border-left:1px solid var(--border-default)}.catalog-tree-row{display:grid;grid-template-columns:30px 22px minmax(180px,1fr) 80px 70px 84px;align-items:center;gap:8px;min-height:54px;padding:7px 16px;border-bottom:1px solid var(--border-subtle,var(--border-default))}.catalog-tree-item:last-child>.catalog-tree-row{border-bottom:0}.catalog-tree-item.is-filtered{display:none}.catalog-tree-item.is-dragging>.catalog-tree-row{opacity:.55;background:var(--blue-50)}.catalog-drag{display:grid;place-items:center;width:30px;height:30px;padding:0;border:0;background:transparent;color:var(--text-tertiary);cursor:grab}.catalog-drag:active{cursor:grabbing}.catalog-tree-branch{color:var(--text-tertiary)}.catalog-tree-name{min-width:0}.catalog-tree-name b,.catalog-tree-name small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.catalog-tree-name b{font-size:13px}.catalog-tree-name small{font-size:11px;color:var(--text-secondary)}.catalog-tree-count{text-align:center;font-size:12px;color:var(--text-secondary)}.catalog-tree-actions{display:flex;justify-content:flex-end;gap:4px}.catalog-action-edit{color:var(--blue-600)}.catalog-action-delete{color:var(--red-600)}.catalog-card-body{padding:18px 20px}.catalog-settings-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;padding:0 20px 20px}.catalog-settings-grid>section{min-width:0}.catalog-settings-grid h3{margin:0 0 4px;font-size:14px}.catalog-check-list,.catalog-filter-list{display:grid;gap:6px;max-height:440px;margin-top:12px;overflow-y:auto}.catalog-check-list label,.catalog-filter-row{min-width:0;padding:9px 10px;border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--background-card)}.catalog-check-list label{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:9px;align-items:center}.catalog-check-list small{grid-column:2;color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.catalog-filter-row{display:grid;grid-template-columns:minmax(0,1fr) minmax(160px,210px);align-items:center;gap:10px}.catalog-filter-row label{display:flex;align-items:center;gap:9px;min-width:0}.catalog-filter-row label span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.catalog-form-footer{display:flex;justify-content:flex-end;padding:14px 20px;border-top:1px solid var(--border-default)}.catalog-drawer{width:min(1000px,66.666vw);max-width:none}.catalog-item-form{display:block;overflow-x:hidden}.catalog-drawer-tabs{position:sticky;top:0;z-index:2;margin:-20px -20px 18px;padding:12px 20px;border-bottom:1px solid var(--border-default);background:var(--background-card)}.catalog-drawer-tabs .tab{text-decoration:none}.catalog-item-form [data-catalog-drawer-panel]{min-width:0}@media(max-width:900px){.catalog-settings-grid{grid-template-columns:1fr}.catalog-drawer{width:min(100%,720px)}.catalog-tree-row{grid-template-columns:30px 18px minmax(140px,1fr) 70px 70px}.catalog-tree-count{display:none}}@media(max-width:640px){.catalog-section-head{align-items:stretch;flex-direction:column}.catalog-tree-search{width:100%}.catalog-tree-row{grid-template-columns:28px minmax(120px,1fr) 70px 76px}.catalog-tree-branch{display:none}.catalog-tree-children{margin-left:16px}.catalog-settings-grid{padding:0 14px 14px}.catalog-filter-row{grid-template-columns:1fr}.catalog-table th:nth-child(3),.catalog-table td:nth-child(3){display:none}}
.catalog-panel+.catalog-panel{margin-top:22px}
.catalog-product-card-settings{margin-top:18px;padding-top:18px;border-top:1px solid var(--border-default)}
.catalog-product-card-layout{display:grid;grid-template-columns:minmax(280px,.8fr) minmax(360px,1.2fr);gap:22px;margin-top:14px}
diff --git a/adminx/modules/Catalog/view/edit.twig b/adminx/modules/Catalog/view/edit.twig
index 24cd541..498f11a 100644
--- a/adminx/modules/Catalog/view/edit.twig
+++ b/adminx/modules/Catalog/view/edit.twig
@@ -38,7 +38,7 @@
Назначение каталога Обычный каталог {% if products_available %}Товарный каталог {% endif %}{% if products_available %}Товарный режим включает коммерческий индекс, цены, остатки, варианты и фиды.{% else %}Товарный режим доступен после установки модуля «Товары».{% endif %}
Запрос фильтра Не выбран {% for item in request_options %}{{ item.title ?: item.alias }}{% if item.alias and item.alias != item.title %} · {{ item.alias }}{% endif %} (#{{ item.id }}) {% endfor %}Запрос, который формирует выдачу и условия фильтрации.
- Навигация Не связана {% for item in navigation_options %}{{ item.title ?: item.alias }}{% if item.alias and item.alias != item.title %} · {{ item.alias }}{% endif %} (#{{ item.id }}) {% endfor %}Меню, используемое для экспорта структуры каталога.
+ Навигация в шапке Не связана {% for item in navigation_options %}{{ item.title ?: item.alias }}{% if item.alias and item.alias != item.title %} · {{ item.alias }}{% endif %} (#{{ item.id }}) {% endfor %}Дополнительные ссылки и подборки перед разделами каталога.
Рубрика разделов Не выбрана {% for item in rubric_options %}{{ item.title ?: item.alias }}{% if item.alias and item.alias != item.title %} · {{ item.alias }}{% endif %} (#{{ item.id }}) {% endfor %}Рубрика документов, связанных с разделами каталога.
{% if products_available %}
Поля товарного представления {% set commerce_fields={'product_title_field_id':'Название товара','product_article_field_id':'Артикул','product_price_field_id':'Цена','product_old_price_field_id':'Старая цена','product_stock_field_id':'Остаток','product_images_field_id':'Изображения'} %}{% for key,label in commerce_fields %}{{ label }} Не назначено {% for group in field_groups %}{% for field in group.items %}{{ field.title ?: field.alias }} · #{{ field.id }} {% endfor %}{% endfor %} {% endfor %}
diff --git a/adminx/modules/Customers/assets/customers.css b/adminx/modules/Customers/assets/customers.css
index 38c64dd..d1e5077 100644
--- a/adminx/modules/Customers/assets/customers.css
+++ b/adminx/modules/Customers/assets/customers.css
@@ -33,9 +33,6 @@
.customers-table td:last-child {
text-align: right;
}
-.customers-table td:first-child {
- font-variant-numeric: tabular-nums;
-}
.customer-editor-drawer {
width: min(66.666vw, 1240px);
max-width: none;
@@ -83,7 +80,6 @@
min-width: 0;
overflow: hidden;
font-size: 13px;
- font-variant-numeric: tabular-nums;
text-overflow: ellipsis;
white-space: nowrap;
}
diff --git a/adminx/modules/Dashboard/assets/dashboard.css b/adminx/modules/Dashboard/assets/dashboard.css
index dc79a97..295abd0 100644
--- a/adminx/modules/Dashboard/assets/dashboard.css
+++ b/adminx/modules/Dashboard/assets/dashboard.css
@@ -10,7 +10,6 @@
gap: 8px;
color: var(--text-secondary);
font-size: 13px;
- font-variant-numeric: tabular-nums;
}
/* ── KPI ───────────────────────────────────────────── */
.dashboard-kpis {
@@ -62,7 +61,6 @@
color: var(--text-primary);
font-size: 24px;
line-height: 1.1;
- font-variant-numeric: tabular-nums;
overflow-wrap: anywhere;
}
.dashboard-delta {
@@ -75,7 +73,6 @@
border-radius: 999px;
font-size: 12px;
font-weight: 600;
- font-variant-numeric: tabular-nums;
}
.dashboard-delta .ti {
font-size: 14px;
@@ -154,7 +151,6 @@
color: var(--text-primary);
font-size: 18px;
font-weight: 700;
- font-variant-numeric: tabular-nums;
}
.dashboard-chart-legend small {
color: var(--text-secondary);
@@ -216,7 +212,6 @@
border-top: 1px solid var(--border-default);
color: var(--text-muted);
font-size: 11px;
- font-variant-numeric: tabular-nums;
}
.dashboard-chart-axis .is-weekend {
color: var(--text-secondary);
@@ -253,7 +248,6 @@
.dashboard-status-count {
font-weight: 700;
font-size: 13px;
- font-variant-numeric: tabular-nums;
}
.dashboard-status-bar {
grid-column: 1 / -1;
@@ -350,7 +344,6 @@
.dashboard-money {
font-weight: 700;
white-space: nowrap;
- font-variant-numeric: tabular-nums;
}
.dashboard-status-tag {
display: flex;
@@ -412,7 +405,6 @@
margin-top: 4px;
color: var(--text-secondary);
font-size: 11.5px;
- font-variant-numeric: tabular-nums;
}
.dashboard-document-list > a > .ti {
color: var(--text-muted);
diff --git a/adminx/modules/Database/Backup.php b/adminx/modules/Database/Backup.php
index e6c9275..e6ef436 100644
--- a/adminx/modules/Database/Backup.php
+++ b/adminx/modules/Database/Backup.php
@@ -58,7 +58,7 @@
'size' => filesize($path),
'size_h' => Model::human(filesize($path)),
'mtime' => filemtime($path),
- 'own' => strpos($f, 'adminx_') === 0,
+ 'own' => self::isManagedName($f),
);
}
@@ -111,7 +111,7 @@
}
$dir = self::ensureDir();
- $name = self::uniqueName('adminx_uploaded_' . date('Ymd_His'), $extension);
+ $name = self::uniqueName('avecms_uploaded_' . date('Ymd_His'), $extension);
$temporary = $dir . DS . '.upload-' . bin2hex(random_bytes(6)) . $extension;
if (!move_uploaded_file($file['tmp_name'], $temporary)) {
throw new \RuntimeException('Не удалось сохранить загруженный файл');
@@ -151,11 +151,11 @@
$prefixes = Model::prefixes();
$purpose = preg_replace('/[^a-z0-9_-]+/', '', strtolower((string) $purpose));
- $name = self::uniqueName('adminx_' . ($purpose !== '' ? $purpose . '_' : '') . implode('-', $prefixes) . '_' . date('Ymd_His'), '.sql.gz');
+ $name = self::uniqueName('avecms_' . ($purpose !== '' ? $purpose . '_' : '') . implode('-', $prefixes) . '_' . date('Ymd_His'), '.sql.gz');
$path = $dir . DS . $name;
$temporary = $path . '.part';
- $lockPath = $dir . DS . '.adminx-backup.lock';
+ $lockPath = $dir . DS . '.avecms-backup.lock';
$lock = fopen($lockPath, 'c');
if (!$lock || !flock($lock, LOCK_EX | LOCK_NB)) {
if ($lock) { fclose($lock); }
@@ -169,7 +169,7 @@
throw new \RuntimeException('Не удалось создать файл дампа');
}
- gzwrite($fp, "-- adminx full dump\n-- database: " . Model::databaseName() . "\n-- prefixes: {{prefix}}\n-- source-prefix: " . Model::basePrefix() .
+ gzwrite($fp, "-- AVE.cms full dump\n-- database: " . Model::databaseName() . "\n-- prefixes: {{prefix}}\n-- source-prefix: " . Model::basePrefix() .
"\n-- date: " . date('Y-m-d H:i:s') . "\n\nSET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS=0;\n\n");
$mysqli = DB::mysqli();
@@ -205,6 +205,13 @@
return array('name' => $name, 'size' => filesize($path), 'tables' => count($tables), 'prefixes' => $prefixes);
}
+ /** Новые имена AVE.cms и старые adminx-дампы поддерживаются одинаково. */
+ public static function isManagedName($name)
+ {
+ $name = basename((string) $name);
+ return strpos($name, 'avecms_') === 0 || strpos($name, 'adminx_') === 0;
+ }
+
protected static function dumpTable($fp, $table, $mysqli, $progress = null, $current = 0, $total = 0)
{
$portableTable = Model::portableTableName($table);
diff --git a/adminx/modules/Database/BackupRestore.php b/adminx/modules/Database/BackupRestore.php
index bd151ef..43af874 100644
--- a/adminx/modules/Database/BackupRestore.php
+++ b/adminx/modules/Database/BackupRestore.php
@@ -32,7 +32,7 @@
public static function inspect($name, $includeToken = true)
{
$path = Backup::path($name);
- if ($path === null || strpos(basename($path), 'adminx_') !== 0) {
+ if ($path === null || !Backup::isManagedName($path)) {
throw new \RuntimeException('Для восстановления выберите полный дамп, созданный AVE.cms');
}
diff --git a/adminx/modules/Database/assets/database.css b/adminx/modules/Database/assets/database.css
index 3ed41ed..4ac0f57 100644
--- a/adminx/modules/Database/assets/database.css
+++ b/adminx/modules/Database/assets/database.css
@@ -28,7 +28,6 @@
}
.db-tables-count {
flex: 0 0 auto;
- font-variant-numeric: tabular-nums;
}
/* Размеры/отступы заголовка/сводки — общие (.ax-panel-header/.ax-summary). Здесь только акцент. */
.database-panel-header {
@@ -98,7 +97,6 @@
.database-restore-head strong {
color: var(--blue-600);
font-size: 22px;
- font-variant-numeric: tabular-nums;
}
.database-restore-track {
height: 6px;
@@ -132,7 +130,6 @@
.database-restore-current small {
margin-top: 3px;
color: var(--text-secondary);
- font-variant-numeric: tabular-nums;
}
.database-restore-pulse {
width: 10px;
diff --git a/adminx/modules/Database/view/index.twig b/adminx/modules/Database/view/index.twig
index 0479490..8aa8717 100644
--- a/adminx/modules/Database/view/index.twig
+++ b/adminx/modules/Database/view/index.twig
@@ -151,7 +151,7 @@
{{ b.name }}
- {% if b.own %}adminx {% endif %}
+ {% if b.own %}AVE.cms {% endif %}
{{ b.size_h }}
diff --git a/adminx/modules/Directories/Controller.php b/adminx/modules/Directories/Controller.php
new file mode 100644
index 0000000..49b4839
--- /dev/null
+++ b/adminx/modules/Directories/Controller.php
@@ -0,0 +1,189 @@
+
+ | @copyright 2007-2026 (c) AVE.cms
+ | @link https://ave-cms.ru
+ | @version 3.3
+ */
+
+ namespace App\Adminx\Directories;
+
+ defined('BASEPATH') || die('Direct access to this location is not allowed.');
+
+ use App\Common\AdminAssets;
+ use App\Common\AuditLog;
+ use App\Common\Auth;
+ use App\Common\Controller as BaseController;
+ use App\Common\Permission;
+ use App\Content\Directories\DirectoryRepository;
+ use App\Helpers\Request;
+
+ class Controller extends BaseController
+ {
+ public function index(array $params = array())
+ {
+ if (!Permission::check('view_rubrics')) {
+ return $this->renderStatus('@adminx/404.twig', array('title' => 'Недостаточно прав'), 403);
+ }
+
+ AdminAssets::addStyle($this->base() . '/modules/Rubrics/assets/rubrics.css', 49);
+ AdminAssets::addStyle($this->base() . '/modules/Directories/assets/directories.css', 50);
+ AdminAssets::addScript($this->base() . '/modules/Directories/assets/directories.js', 50);
+ $query = Request::getStr('q', '');
+ $directories = Model::all($query);
+ $selectedId = max(0, Request::getInt('id', 0));
+ $selected = $selectedId > 0 ? DirectoryRepository::find($selectedId) : null;
+ if ($selected) {
+ $selectedUsage = null;
+ foreach ($directories as $directory) {
+ if ((int) $directory['id'] === $selectedId) {
+ $selectedUsage = (int) $directory['usage_count'];
+ break;
+ }
+ }
+
+ $selected['usage_count'] = $selectedUsage === null
+ ? DirectoryRepository::usageCount($selectedId)
+ : $selectedUsage;
+ }
+
+ return $this->render('@directories/index.twig', array(
+ 'directories' => $directories,
+ 'selected' => $selected,
+ 'items' => $selected ? DirectoryRepository::items($selectedId) : array(),
+ 'stats' => Model::stats(),
+ 'schema_ready' => Model::schemaReady(),
+ 'filters' => array('q' => $query),
+ 'can_manage' => Permission::check('manage_rubrics'),
+ ));
+ }
+
+ public function store(array $params = array())
+ {
+ if (($error = $this->manageGuard()) !== null) {
+ return $error;
+ }
+
+ return $this->saveDirectory(0, 'Справочник создан', 'directory.created');
+ }
+
+ public function update(array $params = array())
+ {
+ if (($error = $this->manageGuard()) !== null) {
+ return $error;
+ }
+
+ return $this->saveDirectory(isset($params['id']) ? (int) $params['id'] : 0, 'Справочник сохранён', 'directory.updated');
+ }
+
+ public function delete(array $params = array())
+ {
+ if (($error = $this->manageGuard()) !== null) {
+ return $error;
+ }
+
+ $id = isset($params['id']) ? (int) $params['id'] : 0;
+ try {
+ if (!DirectoryRepository::delete($id)) {
+ return $this->error('Справочник не найден', array(), 404);
+ }
+
+ $this->audit('directory.deleted', $id);
+ return $this->success('Справочник удалён', array(
+ 'redirect' => $this->base() . '/directories',
+ ));
+ } catch (\Throwable $e) {
+ return $this->error($e->getMessage(), array(), 422);
+ }
+ }
+
+ public function storeItem(array $params = array())
+ {
+ if (($error = $this->manageGuard()) !== null) {
+ return $error;
+ }
+
+ return $this->saveItem(isset($params['id']) ? (int) $params['id'] : 0, 0, 'Значение добавлено', 'directory.item_created');
+ }
+
+ public function updateItem(array $params = array())
+ {
+ if (($error = $this->manageGuard()) !== null) {
+ return $error;
+ }
+
+ return $this->saveItem(
+ isset($params['id']) ? (int) $params['id'] : 0,
+ isset($params['item']) ? (int) $params['item'] : 0,
+ 'Значение сохранено',
+ 'directory.item_updated'
+ );
+ }
+
+ public function deleteItem(array $params = array())
+ {
+ if (($error = $this->manageGuard()) !== null) {
+ return $error;
+ }
+
+ $directoryId = isset($params['id']) ? (int) $params['id'] : 0;
+ $itemId = isset($params['item']) ? (int) $params['item'] : 0;
+ if (!DirectoryRepository::deleteItem($directoryId, $itemId)) {
+ return $this->error('Значение не найдено', array(), 404);
+ }
+
+ $this->audit('directory.item_deleted', $directoryId, array('item_id' => $itemId));
+ return $this->success('Значение удалено', array(
+ 'redirect' => $this->base() . '/directories?id=' . $directoryId,
+ ));
+ }
+
+ protected function saveDirectory($id, $message, $action)
+ {
+ try {
+ $directory = DirectoryRepository::save($id, Request::postAll());
+ $this->audit($action, $directory['id'], array('code' => $directory['code']));
+ return $this->success($message, array(
+ 'data' => $directory,
+ 'redirect' => $this->base() . '/directories?id=' . $directory['id'],
+ ));
+ } catch (\Throwable $e) {
+ return $this->error($e->getMessage(), array(), 422);
+ }
+ }
+
+ protected function saveItem($directoryId, $itemId, $message, $action)
+ {
+ try {
+ $item = DirectoryRepository::saveItem($directoryId, $itemId, Request::postAll());
+ $this->audit($action, $directoryId, array('item_id' => $item['id'], 'key' => $item['item_key']));
+ return $this->success($message, array(
+ 'data' => $item,
+ 'redirect' => $this->base() . '/directories?id=' . $directoryId,
+ ));
+ } catch (\Throwable $e) {
+ return $this->error($e->getMessage(), array(), 422);
+ }
+ }
+
+ protected function manageGuard()
+ {
+ return $this->guardPermission('manage_rubrics');
+ }
+
+ protected function audit($action, $directoryId, array $meta = array())
+ {
+ AuditLog::record($action, array(
+ 'actor_id' => Auth::id(),
+ 'target_type' => 'directory',
+ 'target_id' => (int) $directoryId,
+ 'meta' => $meta,
+ ));
+ }
+ }
diff --git a/adminx/modules/Directories/Model.php b/adminx/modules/Directories/Model.php
new file mode 100644
index 0000000..4657d8b
--- /dev/null
+++ b/adminx/modules/Directories/Model.php
@@ -0,0 +1,55 @@
+
+ | @copyright 2007-2026 (c) AVE.cms
+ | @link https://ave-cms.ru
+ | @version 3.3
+ */
+
+ namespace App\Adminx\Directories;
+
+ defined('BASEPATH') || die('Direct access to this location is not allowed.');
+
+ use App\Content\Directories\DirectoryRepository;
+
+ /** Admin adapter over the content-domain directory repository. */
+ class Model
+ {
+ public static function all($query = '')
+ {
+ $rows = DirectoryRepository::all($query);
+ $usage = DirectoryRepository::usageMap();
+ foreach ($rows as &$row) {
+ $row['usage_count'] = isset($usage[$row['id']]) ? (int) $usage[$row['id']] : 0;
+ }
+
+ unset($row);
+ return $rows;
+ }
+
+ public static function stats()
+ {
+ $rows = DirectoryRepository::all();
+ $stats = array('total' => count($rows), 'active' => 0, 'values' => 0);
+ foreach ($rows as $row) {
+ if ($row['is_active']) {
+ $stats['active']++;
+ }
+
+ $stats['values'] += (int) $row['items_count'];
+ }
+
+ return $stats;
+ }
+
+ public static function schemaReady()
+ {
+ return DirectoryRepository::schemaReady();
+ }
+ }
diff --git a/adminx/modules/Directories/assets/directories.css b/adminx/modules/Directories/assets/directories.css
new file mode 100644
index 0000000..7f7863a
--- /dev/null
+++ b/adminx/modules/Directories/assets/directories.css
@@ -0,0 +1,150 @@
+.content-model-tabs {
+ margin-bottom: var(--space-5);
+}
+.directories-summary {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: var(--space-4);
+ margin-bottom: var(--space-5);
+}
+.directories-stat {
+ display: flex;
+ align-items: center;
+ gap: var(--space-3);
+ padding: var(--space-4);
+}
+.directories-stat b {
+ display: block;
+ font-size: 20px;
+ line-height: 1.1;
+ font-weight: 800;
+}
+.directories-stat div > span {
+ color: var(--text-secondary);
+ font-size: 13px;
+}
+.directories-panel [hidden],
+#directoryDrawer [hidden],
+#directoryItemDrawer [hidden] {
+ display: none !important;
+}
+.directories-filter .input-wrap {
+ max-width: 560px;
+}
+.directories-table {
+ min-width: 760px;
+}
+.directories-col-name {
+ width: 34%;
+}
+.directories-col-code {
+ width: 18%;
+}
+.directories-col-values {
+ width: 100px;
+}
+.directories-col-usage {
+ width: 150px;
+}
+.directories-col-state {
+ width: 120px;
+}
+.directories-col-actions {
+ width: 92px;
+}
+.directories-name {
+ display: grid;
+ gap: 3px;
+ min-width: 0;
+}
+.directories-name b,
+.directories-name small {
+ overflow-wrap: anywhere;
+ white-space: normal;
+}
+.directories-name small {
+ color: var(--text-secondary);
+ font-size: 12px;
+}
+.directories-table tr.is-selected > td {
+ background: var(--blue-50);
+}
+.directories-values-card {
+ margin-top: var(--space-4);
+}
+.directories-head-actions,
+.directories-actions {
+ flex-wrap: nowrap;
+}
+.directories-actions {
+ justify-content: flex-end;
+ width: max-content;
+ margin-left: auto;
+}
+.directories-action-values {
+ color: var(--blue-600);
+}
+.directories-action-edit {
+ color: var(--violet-600);
+}
+.directories-items-table {
+ min-width: 660px;
+ table-layout: fixed;
+}
+.directories-item-order {
+ width: 92px;
+}
+.directories-item-key {
+ width: 190px;
+}
+.directories-item-state {
+ width: 124px;
+}
+.directories-item-actions {
+ width: 104px;
+}
+.directories-items-table tr.is-muted {
+ opacity: 0.55;
+}
+@media (max-width: 640px) {
+ .directories-summary {
+ grid-template-columns: 1fr;
+ }
+ .directories-values-card .rubrics-section-head {
+ align-items: stretch;
+ flex-direction: column;
+ }
+ .directories-head-actions {
+ justify-content: flex-end;
+ width: 100%;
+ }
+ .directories-table tbody tr {
+ grid-template-columns: minmax(0, 1fr) auto;
+ }
+ .directories-table .directories-cell-name,
+ .directories-table .directories-cell-code {
+ grid-column: 1 / -1;
+ }
+ .directories-table .directories-cell-code,
+ .directories-table .directories-cell-values,
+ .directories-table .directories-cell-usage,
+ .directories-table .directories-cell-state,
+ .directories-table .directories-cell-actions {
+ display: grid;
+ gap: 4px;
+ align-content: start;
+ justify-items: start;
+ }
+ .directories-table td[data-label]::before {
+ content: attr(data-label);
+ color: var(--text-secondary);
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: 0;
+ text-transform: uppercase;
+ }
+ .directories-table .directories-cell-actions {
+ align-content: end;
+ justify-items: end;
+ }
+}
diff --git a/adminx/modules/Directories/assets/directories.js b/adminx/modules/Directories/assets/directories.js
new file mode 100644
index 0000000..195d9dc
--- /dev/null
+++ b/adminx/modules/Directories/assets/directories.js
@@ -0,0 +1,179 @@
+(function (window, document) {
+ 'use strict';
+
+ var Adminx = window.Adminx || (window.Adminx = {});
+
+ Adminx.Directories = {
+ page: null,
+ directoryForm: null,
+ itemForm: null,
+
+ init: function () {
+ this.page = document.querySelector('[data-directories-page]');
+ if (!this.page) { return; }
+
+ this.directoryForm = document.querySelector('[data-directory-form]');
+ this.itemForm = document.querySelector('[data-directory-item-form]');
+ var self = this;
+
+ document.addEventListener('click', function (event) {
+ if (event.target.closest('[data-directory-new]')) { self.newDirectory(); return; }
+ var edit = event.target.closest('[data-directory-edit]');
+ if (edit) { self.editDirectory(edit); return; }
+ if (event.target.closest('[data-directory-delete]')) { self.deleteDirectory(); return; }
+ if (event.target.closest('[data-directory-item-new]')) { self.newItem(); return; }
+ var itemEdit = event.target.closest('[data-directory-item-edit]');
+ if (itemEdit) { self.editItem(itemEdit); return; }
+ var itemDelete = event.target.closest('[data-directory-item-delete]');
+ if (itemDelete) { self.deleteItem(itemDelete); return; }
+ });
+
+ if (this.directoryForm) {
+ this.directoryForm.addEventListener('submit', function (event) {
+ event.preventDefault();
+ var id = self.directoryForm.elements.id.value;
+ self.submit(self.base() + '/directories' + (id ? '/' + id : ''), self.directoryForm);
+ });
+ }
+
+ if (this.itemForm) {
+ this.itemForm.addEventListener('submit', function (event) {
+ event.preventDefault();
+ var directoryId = self.itemForm.getAttribute('data-directory');
+ var itemId = self.itemForm.elements.id.value;
+ self.submit(self.base() + '/directories/' + directoryId + '/items' + (itemId ? '/' + itemId : ''), self.itemForm);
+ });
+ }
+ },
+
+ base: function () {
+ return this.page.getAttribute('data-base') || '';
+ },
+
+ newDirectory: function () {
+ if (!this.directoryForm) { return; }
+ this.directoryForm.reset();
+ this.directoryForm.elements.id.value = '';
+ this.directoryForm.elements.is_active.checked = true;
+ this.directoryForm.setAttribute('data-usage', '0');
+ this.directoryForm.querySelector('[data-directory-delete]').hidden = true;
+ document.querySelector('[data-directory-form-title]').textContent = 'Новый справочник';
+ Adminx.Drawer.open('directoryDrawer');
+ },
+
+ editDirectory: function (button) {
+ if (!this.directoryForm) { return; }
+ this.directoryForm.reset();
+ this.directoryForm.elements.id.value = button.getAttribute('data-id') || '';
+ this.directoryForm.elements.name.value = button.getAttribute('data-name') || '';
+ this.directoryForm.elements.code.value = button.getAttribute('data-code') || '';
+ this.directoryForm.elements.description.value = button.getAttribute('data-description') || '';
+ this.directoryForm.elements.is_active.checked = button.getAttribute('data-active') === '1';
+ this.directoryForm.setAttribute('data-usage', button.getAttribute('data-usage') || '0');
+ this.directoryForm.querySelector('[data-directory-delete]').hidden = false;
+ document.querySelector('[data-directory-form-title]').textContent = 'Настройки справочника';
+ Adminx.Drawer.open('directoryDrawer');
+ },
+
+ deleteDirectory: function () {
+ if (!this.directoryForm) { return; }
+ var self = this;
+ var id = this.directoryForm.elements.id.value;
+ var name = this.directoryForm.elements.name.value;
+ var usage = parseInt(this.directoryForm.getAttribute('data-usage'), 10) || 0;
+ Adminx.Confirm.open({
+ kind: usage > 0 ? 'warning' : 'danger',
+ title: usage > 0 ? 'Справочник используется' : 'Удалить справочник?',
+ message: usage > 0
+ ? 'Он подключён к полям: ' + usage + '. Сначала выберите для них другой источник значений.'
+ : '«' + name + '» и все его значения будут удалены без восстановления.',
+ confirmLabel: usage > 0 ? 'Понятно' : 'Удалить',
+ confirmClass: usage > 0 ? 'btn-secondary' : 'btn-danger',
+ onConfirm: function () {
+ if (usage > 0) { return; }
+ var data = new FormData();
+ data.append('_csrf', self.csrf());
+ self.submit(self.base() + '/directories/' + id + '/delete', data);
+ }
+ });
+ },
+
+ newItem: function () {
+ if (!this.itemForm) { return; }
+ this.itemForm.reset();
+ this.itemForm.elements.id.value = '';
+ this.itemForm.elements.sort_order.value = '0';
+ this.itemForm.elements.is_active.checked = true;
+ document.querySelector('[data-directory-item-title]').textContent = 'Новое значение';
+ Adminx.Drawer.open('directoryItemDrawer');
+ },
+
+ editItem: function (button) {
+ if (!this.itemForm) { return; }
+ this.itemForm.reset();
+ this.itemForm.elements.id.value = button.getAttribute('data-id') || '';
+ this.itemForm.elements.item_key.value = button.getAttribute('data-key') || '';
+ this.itemForm.elements.label.value = button.getAttribute('data-label') || '';
+ this.itemForm.elements.sort_order.value = button.getAttribute('data-order') || '0';
+ this.itemForm.elements.is_active.checked = button.getAttribute('data-active') === '1';
+ document.querySelector('[data-directory-item-title]').textContent = 'Изменить значение';
+ Adminx.Drawer.open('directoryItemDrawer');
+ },
+
+ deleteItem: function (button) {
+ var self = this;
+ var itemId = button.getAttribute('data-directory-item-delete');
+ var directoryId = this.itemForm ? this.itemForm.getAttribute('data-directory') : '';
+ Adminx.Confirm.open({
+ kind: 'danger',
+ title: 'Удалить значение?',
+ message: '«' + (button.getAttribute('data-label') || '') + '» исчезнет из выбора. Уже сохранённые документы сохранят ключ.',
+ confirmLabel: 'Удалить',
+ confirmClass: 'btn-danger',
+ onConfirm: function () {
+ var data = new FormData();
+ data.append('_csrf', self.csrf());
+ self.submit(self.base() + '/directories/' + directoryId + '/items/' + itemId + '/delete', data);
+ }
+ });
+ },
+
+ submit: function (url, body) {
+ var self = this;
+ Adminx.Loader.show();
+ var data = body instanceof FormData ? body : new FormData(body);
+ fetch(url, {
+ method: 'POST',
+ body: data,
+ credentials: 'same-origin',
+ headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
+ }).then(function (response) {
+ return response.json().then(function (payload) {
+ if (!response.ok || !payload.success) {
+ throw new Error(payload.message || 'Не удалось сохранить данные');
+ }
+
+ return payload;
+ });
+ }).then(function (payload) {
+ Adminx.Toast.show(payload.message || 'Сохранено', 'success');
+ window.location.href = payload.redirect || window.location.href;
+ }).catch(function (error) {
+ Adminx.Toast.show(error.message || 'Ошибка запроса', 'danger');
+ }).finally(function () {
+ Adminx.Loader.hide();
+ });
+ },
+
+ csrf: function () {
+ var input = document.querySelector('[data-directory-form] [name="_csrf"]');
+ return input ? input.value : '';
+ }
+ };
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', function () { Adminx.Directories.init(); });
+ } else {
+ Adminx.Directories.init();
+ }
+})(window, document);
diff --git a/adminx/modules/Directories/migrations/001_create_directories.sql b/adminx/modules/Directories/migrations/001_create_directories.sql
new file mode 100644
index 0000000..7d4a6c2
--- /dev/null
+++ b/adminx/modules/Directories/migrations/001_create_directories.sql
@@ -0,0 +1,30 @@
+CREATE TABLE IF NOT EXISTS `{{content_prefix}}_directories` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `code` VARCHAR(64) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
+ `name` VARCHAR(190) NOT NULL,
+ `description` TEXT NULL,
+ `settings_json` TEXT NULL,
+ `is_active` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1,
+ `created_at` INT UNSIGNED NOT NULL DEFAULT 0,
+ `updated_at` INT UNSIGNED NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uniq_directory_code` (`code`),
+ KEY `idx_directory_active_name` (`is_active`, `name`)
+) ENGINE=InnoDB ROW_FORMAT=DYNAMIC DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE IF NOT EXISTS `{{content_prefix}}_directory_items` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `directory_id` INT UNSIGNED NOT NULL,
+ `item_key` VARCHAR(120) NOT NULL,
+ `label` VARCHAR(255) NOT NULL,
+ `sort_order` INT UNSIGNED NOT NULL DEFAULT 0,
+ `is_active` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1,
+ `parent_id` INT UNSIGNED NOT NULL DEFAULT 0,
+ `data_json` TEXT NULL,
+ `created_at` INT UNSIGNED NOT NULL DEFAULT 0,
+ `updated_at` INT UNSIGNED NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uniq_directory_item_key` (`directory_id`, `item_key`(120)),
+ KEY `idx_directory_item_order` (`directory_id`, `sort_order`, `id`),
+ KEY `idx_directory_item_parent` (`directory_id`, `parent_id`)
+) ENGINE=InnoDB ROW_FORMAT=DYNAMIC DEFAULT CHARSET=utf8mb4;
diff --git a/adminx/modules/Directories/module.php b/adminx/modules/Directories/module.php
new file mode 100644
index 0000000..1dd8f1f
--- /dev/null
+++ b/adminx/modules/Directories/module.php
@@ -0,0 +1,40 @@
+
+ | @copyright 2007-2026 (c) AVE.cms
+ | @link https://ave-cms.ru
+ | @version 3.3
+ */
+
+ defined('BASEPATH') || die('Direct access to this location is not allowed.');
+
+ return array(
+ 'code' => 'directories',
+ 'name' => 'Справочники',
+ 'version' => '0.2.0',
+ 'requires' => array('rubrics'),
+
+ 'routes' => array(
+ array('GET', '/directories', array(\App\Adminx\Directories\Controller::class, 'index'), array('permission' => 'view_rubrics')),
+ array('POST', '/directories', array(\App\Adminx\Directories\Controller::class, 'store'), array('permission' => 'manage_rubrics')),
+ array('POST', '/directories/{id}', array(\App\Adminx\Directories\Controller::class, 'update'), array('permission' => 'manage_rubrics')),
+ array('POST', '/directories/{id}/delete', array(\App\Adminx\Directories\Controller::class, 'delete'), array('permission' => 'manage_rubrics')),
+ array('POST', '/directories/{id}/items', array(\App\Adminx\Directories\Controller::class, 'storeItem'), array('permission' => 'manage_rubrics')),
+ array('POST', '/directories/{id}/items/{item}', array(\App\Adminx\Directories\Controller::class, 'updateItem'), array('permission' => 'manage_rubrics')),
+ array('POST', '/directories/{id}/items/{item}/delete', array(\App\Adminx\Directories\Controller::class, 'deleteItem'), array('permission' => 'manage_rubrics')),
+ ),
+
+ 'migrations' => array(
+ array('id' => '001_create_directories', 'file' => 'migrations/001_create_directories.sql'),
+ ),
+
+ 'view_globals' => array(
+ 'module_code' => 'directories',
+ ),
+ );
diff --git a/adminx/modules/Directories/view/index.twig b/adminx/modules/Directories/view/index.twig
new file mode 100644
index 0000000..c63ff1c
--- /dev/null
+++ b/adminx/modules/Directories/view/index.twig
@@ -0,0 +1,212 @@
+{% extends '@adminx/main.twig' %}
+{% block title %}Справочники{% endblock %}
+{% block content %}
+
+
+ Главная
+ Рубрики и поля
+ Справочники
+
+
+
+
+{% if not schema_ready %}
+
+
+
+
Схема справочников ещё не готова
+
Примените миграции ядра в разделе «База данных → Миграции».
+
+
+{% endif %}
+
+
+
{{ stats.total }} Справочников
+
{{ stats.values }} Значений
+
+
+{% set content_model_section = 'directories' %}
+{% include '@rubrics/_content_tabs.twig' %}
+
+
+
+
+
+
+
+
+
+
Справочники
+
{{ directories|length }} в текущем фильтре, {{ stats.total }} всего.
+
+
+
{{ directories|length }}
+
+
+
+
+
+
+
+
+
+
Список справочников
+
Ключ хранится в документе, подпись можно менять без перезаписи документов.
+
+
+
+
+
+
+ {% if selected %}
+
+
+
+
+
+
Значения: {{ selected.name }}
+
{{ selected.code }} · {{ selected.usage_count }} полей · {{ items|length }} значений
+
+
+ {% if can_manage %}
+
+
+ Добавить значение
+
+ {% endif %}
+
+
+
+ {% endif %}
+
+
+{% if can_manage %}
+
+
+{% if selected %}
+
+{% endif %}
+{% endif %}
+
+{% endblock %}
diff --git a/adminx/modules/Documents/Controller.php b/adminx/modules/Documents/Controller.php
index 4534f03..e100298 100644
--- a/adminx/modules/Documents/Controller.php
+++ b/adminx/modules/Documents/Controller.php
@@ -21,6 +21,7 @@
use App\Common\AuditLog;
use App\Common\Auth;
use App\Common\Controller as BaseController;
+ use App\Adminx\Support\BulkActionExecutor;
use App\Common\ErrorReport;
use App\Common\Permission;
use App\Adminx\Support\CodeEditor;
@@ -36,6 +37,7 @@
use App\Content\Documents\DocumentSnapshotStore;
use App\Content\Documents\FieldTemplateManifest;
use App\Content\Documents\RubricSchemaBuilder;
+ use App\Content\Fields\DocumentRelationIndex;
use App\Frontend\DocumentRevisionPreview;
use App\Helpers\Request;
use DB;
@@ -174,7 +176,11 @@
AdminAssets::addStyle($this->base() . '/modules/Documents/assets/documents.css', 50);
AdminAssets::addScript($this->base() . '/modules/Documents/assets/documents.js', 50);
- return $this->render('@documents/api.twig', array('tokens' => ApiTokenRepository::all(), 'can_manage_api' => true));
+ $tokens = array_values(array_filter(ApiTokenRepository::all(), function ($token) {
+ return ApiTokenRepository::hasScope($token, 'documents:read')
+ || ApiTokenRepository::hasScope($token, 'documents:write');
+ }));
+ return $this->render('@documents/api.twig', array('tokens' => $tokens, 'can_manage_api' => true));
}
public function issueApiToken(array $params = array())
@@ -240,6 +246,7 @@
'can_manage' => true,
'quick_edit' => Request::getBool('quick_edit', false),
'actor_id' => Auth::id(),
+ 'document_relations' => DocumentRelationIndex::describe((int) $item['Id']),
));
}
@@ -357,27 +364,31 @@
public function bulk(array $params = array())
{
if (($err = $this->guard()) !== null) { return $err; }
- $postedIds = Request::post('ids', array());
- $ids = is_array($postedIds) ? array_values(array_unique(array_map('intval', $postedIds))) : array();
- $ids = array_values(array_filter($ids, function ($id) { return $id > 0; }));
- if (empty($ids) || count($ids) > 200) { return $this->error('Выберите от 1 до 200 документов', array(), 422); }
$action = Request::postStr('action', '');
- if (!in_array($action, array('publish', 'unpublish', 'delete', 'restore', 'purge'), true)) { return $this->error('Неизвестное пакетное действие', array(), 422); }
- $done = 0; $errors = array();
- foreach ($ids as $id) {
- try {
+ $handler = function ($id) use ($action) {
$doc = Model::one($id);
- if (!$doc) { continue; }
+ if (!$doc) { return false; }
if ($action === 'publish' && empty($doc['document_status'])) { Model::toggleStatus($id); }
elseif ($action === 'unpublish' && !empty($doc['document_status'])) { Model::toggleStatus($id); }
elseif ($action === 'delete') { Model::delete($id); }
elseif ($action === 'restore') { Model::restore($id); }
elseif ($action === 'purge') { Model::purge($id); }
- $done++;
- } catch (\Throwable $e) { $errors[] = '#' . $id . ': ' . $e->getMessage(); }
+ return true;
+ };
+
+ try {
+ $result = BulkActionExecutor::execute($action, Request::post('ids', array()), array(
+ 'publish' => $handler,
+ 'unpublish' => $handler,
+ 'delete' => $handler,
+ 'restore' => $handler,
+ 'purge' => $handler,
+ ), 200);
+ } catch (\InvalidArgumentException $e) {
+ return $this->error($e->getMessage(), array(), 422);
}
- return $this->success('Пакетное действие выполнено', array('data' => array('done' => $done, 'errors' => $errors)));
+ return $this->success('Пакетное действие выполнено', array('data' => $result));
}
public function store(array $params = array())
@@ -389,7 +400,9 @@
$input = Model::prepareAliasInput(Request::postAll(), 0);
$fieldValues = isset($input['fields']) && is_array($input['fields']) ? $input['fields'] : array();
$rubricId = isset($input['rubric_id']) ? (int) $input['rubric_id'] : 0;
- $fieldValues = Model::conditionalFieldValues($rubricId, $fieldValues, 0);
+ $fieldGroups = Model::fieldsForRubric($rubricId, 0);
+ $fieldValues = Model::conditionalFieldValues($rubricId, $fieldValues, 0, $fieldGroups);
+ $fieldValues = Model::computedFieldValues($rubricId, $fieldValues, $input, 0, $fieldGroups);
$input['fields'] = $fieldValues;
try {
DocumentSaveEvents::before('create', 'adminx', 0, $rubricId, Auth::id(), $input, $fieldValues);
@@ -400,11 +413,12 @@
return $this->error('Код рубрики остановил создание документа', array('rubric_code_start' => $e->getMessage()), 422);
}
- $fieldValues = Model::conditionalFieldValues($rubricId, $fieldValues, 0);
+ $fieldValues = Model::conditionalFieldValues($rubricId, $fieldValues, 0, $fieldGroups);
+ $fieldValues = Model::computedFieldValues($rubricId, $fieldValues, $input, 0, $fieldGroups);
$input['fields'] = $fieldValues;
$input = Model::prepareAliasInput($input, 0);
$errors = $this->validate($input, 0);
- $errors = array_merge($errors, Model::validateFieldValues($rubricId, $fieldValues, 0));
+ $errors = array_merge($errors, Model::validateFieldValues($rubricId, $fieldValues, 0, $fieldGroups));
if (!empty($errors)) {
return $this->error('Проверьте поля документа', $errors, 422);
}
@@ -428,7 +442,8 @@
Model::save($id, $input, Auth::id());
}
- Model::saveFields($id, $fieldValues);
+ Model::saveFields($id, $fieldValues, $fieldGroups);
+ DocumentSaveEvents::persisted('create', 'adminx', $id, $rubricId, Auth::id(), $input, $fieldValues);
DB::commit();
$mediaFinalization->commit();
} catch (DocumentHookException $e) {
@@ -490,13 +505,15 @@
$input = Model::prepareAliasInput(Request::postAll(), $id);
$rubricId = $document ? (int) $document['rubric_id'] : (isset($input['rubric_id']) ? (int) $input['rubric_id'] : 0);
$fieldValues = isset($input['fields']) && is_array($input['fields']) ? $input['fields'] : array();
- $fieldValues = Model::conditionalFieldValues($rubricId, $fieldValues, $id);
+ $fieldGroups = Model::fieldsForRubric($rubricId, $id);
+ $fieldValues = Model::conditionalFieldValues($rubricId, $fieldValues, $id, $fieldGroups);
+ $fieldValues = Model::computedFieldValues($rubricId, $fieldValues, $input, $id, $fieldGroups);
$input['fields'] = $fieldValues;
$errors = $this->validate($input, $id);
- $errors = array_merge($errors, Model::validateFieldValues($rubricId, $fieldValues, $id));
+ $errors = array_merge($errors, Model::validateFieldValues($rubricId, $fieldValues, $id, $fieldGroups));
return $this->success('Данные сформированы без сохранения', array('data' => array(
- 'payload' => Model::previewPayload($id, $input, $fieldValues, Auth::id()),
+ 'payload' => Model::previewPayload($id, $input, $fieldValues, Auth::id(), $fieldGroups),
'validation_errors' => $errors,
)));
}
@@ -520,7 +537,9 @@
}
$fieldValues = isset($input['fields']) && is_array($input['fields']) ? $input['fields'] : array();
- $fieldValues = Model::conditionalFieldValues((int) $doc['rubric_id'], $fieldValues, $id);
+ $fieldGroups = Model::fieldsForRubric((int) $doc['rubric_id'], $id);
+ $fieldValues = Model::conditionalFieldValues((int) $doc['rubric_id'], $fieldValues, $id, $fieldGroups);
+ $fieldValues = Model::computedFieldValues((int) $doc['rubric_id'], $fieldValues, $input, $id, $fieldGroups);
$input['fields'] = $fieldValues;
try {
DocumentSaveEvents::before('update', 'adminx', $id, (int) $doc['rubric_id'], Auth::id(), $input, $fieldValues, $doc);
@@ -531,10 +550,11 @@
return $this->error('Код рубрики остановил сохранение документа', array('rubric_code_start' => $e->getMessage()), 422);
}
- $fieldValues = Model::conditionalFieldValues((int) $doc['rubric_id'], $fieldValues, $id);
+ $fieldValues = Model::conditionalFieldValues((int) $doc['rubric_id'], $fieldValues, $id, $fieldGroups);
+ $fieldValues = Model::computedFieldValues((int) $doc['rubric_id'], $fieldValues, $input, $id, $fieldGroups);
$input['fields'] = $fieldValues;
$errors = $this->validate($input, $id);
- $errors = array_merge($errors, Model::validateFieldValues((int) $doc['rubric_id'], $fieldValues, $id));
+ $errors = array_merge($errors, Model::validateFieldValues((int) $doc['rubric_id'], $fieldValues, $id, $fieldGroups));
if (!empty($errors)) {
return $this->error('Проверьте поля документа', $errors, 422);
}
@@ -559,7 +579,8 @@
Model::save($id, $input, Auth::id());
}
- Model::saveFields($id, $fieldValues);
+ Model::saveFields($id, $fieldValues, $fieldGroups);
+ DocumentSaveEvents::persisted('update', 'adminx', $id, (int) $doc['rubric_id'], Auth::id(), $input, $fieldValues, $doc);
DB::commit();
$mediaFinalization->commit();
} catch (EditConflict $e) {
diff --git a/adminx/modules/Documents/Model.php b/adminx/modules/Documents/Model.php
index 9cb7336..fa74246 100644
--- a/adminx/modules/Documents/Model.php
+++ b/adminx/modules/Documents/Model.php
@@ -32,10 +32,12 @@
use App\Content\Documents\DocumentAliasTemplate;
use App\Content\Documents\DocumentMediaDraft;
use App\Content\Documents\DocumentMediaPath;
+ use App\Content\Documents\DocumentSearch;
use App\Common\Settings;
use App\Common\SystemTables;
use App\Common\Lifecycle;
use App\Common\DatabaseSchema;
+ use App\Adminx\Support\AdminLocale;
use App\Adminx\Rubrics\FieldAdminEditors;
use App\Adminx\Catalog\Model as CatalogModel;
@@ -63,26 +65,29 @@
$offset = ($page - 1) * $limit;
$where = ' WHERE 1=1';
$args = array();
+ $score = '0';
+ $scoreArgs = array();
$rubricId = isset($filters['rubric_id']) ? (int) $filters['rubric_id'] : 0;
$q = trim(isset($filters['q']) ? (string) $filters['q'] : '');
$field = self::normalizeSearchField(isset($filters['field']) ? (string) $filters['field'] : '', $rubricId);
if ($q !== '') {
if ($field !== '') {
+ $criteria = DocumentSearch::valueCriteria($q, array('df.field_value', 'dft.field_value'));
// Поиск по значению выбранного поля рубрики (напр. «Артикул»).
$where .= ' AND EXISTS (SELECT 1 FROM ' . self::docFieldsTable() . ' df'
. ' INNER JOIN ' . self::fieldsTable() . ' rf ON rf.Id = df.rubric_field_id'
. ' LEFT JOIN ' . self::docFieldsTextTable() . ' dft ON dft.document_id = df.document_id AND dft.rubric_field_id = df.rubric_field_id'
. ' WHERE df.document_id = d.Id AND rf.rubric_field_title = %s'
- . ' AND (df.field_value LIKE %ss OR dft.field_value LIKE %ss))';
+ . ' AND ' . $criteria['where'] . ')';
$args[] = $field;
- $args[] = $q;
- $args[] = $q;
+ $args = array_merge($args, $criteria['where_args']);
} else {
- $where .= ' AND (d.document_title LIKE %ss OR d.document_alias LIKE %ss OR d.Id = %i)';
- $args[] = $q;
- $args[] = $q;
- $args[] = (int) $q;
+ $criteria = DocumentSearch::criteria($q, 'd');
+ $where .= ' AND ' . $criteria['where'];
+ $args = array_merge($args, $criteria['where_args']);
+ $score = $criteria['score'];
+ $scoreArgs = $criteria['score_args'];
}
}
@@ -105,12 +110,16 @@
$countArgs = array_merge(array('SELECT COUNT(*) FROM ' . self::documentsTable() . ' d' . $where), $args);
$total = (int) call_user_func_array(array('DB', 'query'), $countArgs)->getValue();
- $sql = 'SELECT d.*, r.rubric_title, r.rubric_alias'
+ $sql = 'SELECT d.*, r.rubric_title, r.rubric_alias,(' . $score . ') AS search_relevance'
. ' FROM ' . self::documentsTable() . ' d'
. ' LEFT JOIN ' . self::rubricsTable() . ' r ON r.Id = d.rubric_id'
. $where
- . ' ORDER BY d.document_changed DESC, d.Id DESC LIMIT ' . (int) $limit . ' OFFSET ' . (int) $offset;
- $rows = call_user_func_array(array('DB', 'query'), array_merge(array($sql), $args))->getAll();
+ . ' ORDER BY search_relevance DESC,d.document_changed DESC,d.Id DESC'
+ . ' LIMIT ' . (int) $limit . ' OFFSET ' . (int) $offset;
+ $rows = call_user_func_array(
+ array('DB', 'query'),
+ array_merge(array($sql), $scoreArgs, $args)
+ )->getAll();
$items = array();
foreach ($rows as $row) {
@@ -321,7 +330,7 @@
* Валидация значений полей документа по JSON-настройкам (rubric_field_settings.rules).
* Возвращает ошибки в формате ['fields[
]' => сообщение]. Пусто, если правил нет.
*/
- public static function validateFieldValues($rubricId, array $values, $documentId = 0)
+ public static function validateFieldValues($rubricId, array $values, $documentId = 0, $fieldGroups = null)
{
$rubricId = (int) $rubricId;
if ($rubricId <= 0) {
@@ -330,7 +339,7 @@
$fields = array();
$effectiveValues = array();
- foreach (self::fieldsForRubric($rubricId, (int) $documentId) as $group) {
+ foreach (self::resolveFieldGroups($rubricId, (int) $documentId, $fieldGroups) as $group) {
foreach ($group['items'] as $field) {
$fields[] = $field;
$effectiveValues[(int) $field['Id']] = isset($field['field_value']) ? $field['field_value'] : '';
@@ -344,8 +353,34 @@
return FieldValidator::validateValues($fields, $effectiveValues, self::rubricConditionsEnabled($rubricId));
}
+ public static function computedFieldValues($rubricId, array $values, array $document = array(), $documentId = 0, $fieldGroups = null)
+ {
+ $definitions = array();
+ $effectiveValues = array();
+ foreach (self::resolveFieldGroups((int) $rubricId, (int) $documentId, $fieldGroups) as $group) {
+ foreach ($group['items'] as $field) {
+ $fieldId = (int) $field['Id'];
+ $definitions[$fieldId] = $field;
+ $effectiveValues[$fieldId] = isset($field['field_value']) ? $field['field_value'] : '';
+ }
+ }
+
+ foreach ($values as $fieldId => $value) {
+ $effectiveValues[(int) $fieldId] = $value;
+ }
+
+ $computedValues = \App\Content\Fields\ComputedFieldEvaluator::apply($definitions, $effectiveValues, $document);
+ foreach ($definitions as $fieldId => $field) {
+ if ((string) $field['rubric_field_type'] === 'computed' && array_key_exists($fieldId, $computedValues)) {
+ $values[$fieldId] = $computedValues[$fieldId];
+ }
+ }
+
+ return $values;
+ }
+
/** Remove values of fields hidden or locked by rubric form conditions. */
- public static function conditionalFieldValues($rubricId, array $values, $documentId = 0)
+ public static function conditionalFieldValues($rubricId, array $values, $documentId = 0, $fieldGroups = null)
{
if (!self::rubricConditionsEnabled((int) $rubricId)) {
return $values;
@@ -355,7 +390,7 @@
$fieldMap = array();
$storedValues = array();
$effectiveValues = array();
- foreach (self::fieldsForRubric((int) $rubricId, (int) $documentId) as $group) {
+ foreach (self::resolveFieldGroups((int) $rubricId, (int) $documentId, $fieldGroups) as $group) {
foreach ($group['items'] as $field) {
$fields[] = $field;
$fieldId = (int) $field['Id'];
@@ -406,7 +441,7 @@
return array('raw' => $value);
}
- public static function saveFields($documentId, array $values)
+ public static function saveFields($documentId, array $values, $fieldGroups = null)
{
$documentId = (int) $documentId;
$doc = self::one($documentId);
@@ -414,8 +449,9 @@
throw new \RuntimeException('Документ не найден');
}
- $values = self::conditionalFieldValues((int) $doc['rubric_id'], $values, $documentId);
- $fields = self::fieldsForRubric((int) $doc['rubric_id'], $documentId);
+ $fields = self::resolveFieldGroups((int) $doc['rubric_id'], $documentId, $fieldGroups);
+ $values = self::conditionalFieldValues((int) $doc['rubric_id'], $values, $documentId, $fields);
+ $values = self::computedFieldValues((int) $doc['rubric_id'], $values, $doc, $documentId, $fields);
$catalogAllowed = array();
$catalogFields = array();
foreach ($fields as $group) {
@@ -492,14 +528,16 @@
throw new \RuntimeException('Документ не найден');
}
- $values = self::conditionalFieldValues((int) $doc['rubric_id'], $values, $documentId);
- $errors = self::validateFieldValues((int) $doc['rubric_id'], $values, $documentId);
+ $fieldGroups = self::fieldsForRubric((int) $doc['rubric_id'], $documentId);
+ $values = self::conditionalFieldValues((int) $doc['rubric_id'], $values, $documentId, $fieldGroups);
+ $values = self::computedFieldValues((int) $doc['rubric_id'], $values, $doc, $documentId, $fieldGroups);
+ $errors = self::validateFieldValues((int) $doc['rubric_id'], $values, $documentId, $fieldGroups);
if ($errors) {
throw new \RuntimeException('Поля документа не прошли проверку: ' . implode('; ', array_values($errors)));
}
$fieldMap = array();
- foreach (self::fieldsForRubric((int) $doc['rubric_id'], $documentId) as $group) {
+ foreach ($fieldGroups as $group) {
foreach ($group['items'] as $field) {
$fieldMap[(int) $field['Id']] = $field;
}
@@ -683,7 +721,7 @@
);
}
- public static function previewPayload($id, array $input, array $values, $authorId)
+ public static function previewPayload($id, array $input, array $values, $authorId, $fieldGroups = null)
{
$id = (int) $id;
$existing = $id > 0 ? self::one($id) : null;
@@ -716,7 +754,7 @@
$fields = array();
$skippedFields = array();
- $fieldRows = self::fieldsForRubric($rubricId, $id);
+ $fieldRows = self::resolveFieldGroups($rubricId, $id, $fieldGroups);
$flatFields = array();
foreach ($fieldRows as $group) {
foreach ($group['items'] as $field) {
@@ -775,6 +813,13 @@
);
}
+ protected static function resolveFieldGroups($rubricId, $documentId, $fieldGroups)
+ {
+ return is_array($fieldGroups)
+ ? $fieldGroups
+ : self::fieldsForRubric((int) $rubricId, (int) $documentId);
+ }
+
public static function delete($id)
{
$id = (int) $id;
@@ -903,17 +948,37 @@
throw new \RuntimeException('Сначала переместите документ в корзину');
}
- DB::Delete(self::docFieldsTextTable(), 'document_id = %i', $id);
- DB::Delete(self::docFieldsTable(), 'document_id = %i', $id);
- DB::Delete(self::revisionsTable(), 'doc_id = %i', $id);
- DB::Delete(self::aliasHistoryTable(), 'document_id = %i', $id);
- DB::Delete(self::keywordsTable(), 'document_id = %i', $id);
- DB::Delete(self::tagsTable(), 'document_id = %i', $id);
- DB::Delete(self::remarksTable(), 'document_id = %i', $id);
- DB::Delete(self::viewCountTable(), 'document_id = %i', $id);
- DB::Delete(self::documentsTable(), 'Id = %i', $id);
+ $ownsTransaction = !DB::$transaction_in_progress;
+ if ($ownsTransaction) { DB::startTransaction(); }
+ try {
+ DB::Delete(self::docFieldsTextTable(), 'document_id = %i', $id);
+ DB::Delete(self::docFieldsTable(), 'document_id = %i', $id);
+ DB::Delete(self::revisionsTable(), 'doc_id = %i', $id);
+ DB::Delete(self::aliasHistoryTable(), 'document_id = %i', $id);
+ DB::Delete(self::keywordsTable(), 'document_id = %i', $id);
+ DB::Delete(self::tagsTable(), 'document_id = %i', $id);
+ DB::Delete(self::remarksTable(), 'document_id = %i', $id);
+ DB::Delete(self::viewCountTable(), 'document_id = %i', $id);
+ \App\Content\Fields\DocumentRelationIndex::removeDocument($id);
+ DB::Delete(self::documentsTable(), 'Id = %i', $id);
+ if ($ownsTransaction) { DB::commit(); }
+ } catch (\Throwable $e) {
+ if ($ownsTransaction) { DB::rollback(); }
+ throw $e;
+ }
+
self::clearDocumentCache($id);
CatalogModel::reindexDocument($id);
+ Lifecycle::event(
+ 'content.document.deleted',
+ 'document',
+ 'deleted',
+ $id,
+ array(),
+ true,
+ array('hard_delete' => true),
+ 'adminx_documents'
+ );
return true;
}
@@ -1262,45 +1327,7 @@
public static function documentPicker($q, $rubricId = 0, $limit = 20)
{
- $limit = max(1, min(50, (int) $limit));
- $sql = 'SELECT d.Id, d.rubric_id, d.document_title, d.document_alias, r.rubric_title'
- . ' FROM ' . self::documentsTable() . ' d'
- . ' LEFT JOIN ' . self::rubricsTable() . ' r ON r.Id = d.rubric_id'
- . " WHERE d.document_deleted != '1'";
- $args = array();
- $q = trim((string) $q);
- if ($q !== '') {
- $sql .= ' AND (d.document_title LIKE %ss OR d.document_alias LIKE %ss OR d.Id = %i)';
- $args[] = $q;
- $args[] = $q;
- $args[] = (int) $q;
- }
-
- $rubricIds = array();
- foreach (explode(',', (string) $rubricId) as $r) {
- $r = (int) trim($r);
- if ($r > 0) { $rubricIds[] = $r; }
- }
-
- if (!empty($rubricIds)) {
- $rubricIds = array_values(array_unique($rubricIds));
- $sql .= ' AND d.rubric_id IN (' . implode(',', array_map('intval', $rubricIds)) . ')';
- }
-
- $sql .= ' ORDER BY d.document_changed DESC, d.Id DESC LIMIT ' . (int) $limit;
- $rows = call_user_func_array(array('DB', 'query'), array_merge(array($sql), $args))->getAll();
- $out = array();
- foreach ($rows as $row) {
- $out[] = array(
- 'id' => (int) $row['Id'],
- 'rubric_id' => (int) $row['rubric_id'],
- 'title' => self::decode($row['document_title']),
- 'alias' => (string) $row['document_alias'],
- 'rubric_title' => self::decode(isset($row['rubric_title']) ? $row['rubric_title'] : ''),
- );
- }
-
- return $out;
+ return (new \App\Content\Documents\DocumentPickerRepository())->search($q, $rubricId, $limit);
}
/** Existing keywords/tags for the searchable tag inputs in document editor. */
@@ -2185,14 +2212,17 @@
}
}
+ $emptyLabel = AdminLocale::translateMarkup('Разделы не выбраны.');
+ $addLabel = AdminLocale::translateMarkup('Добавить раздел');
+ $searchLabel = AdminLocale::translateMarkup('Найти раздел');
$html = ''
. '
'
. '
' . $tokens . '
'
- . '
Разделы не выбраны.
'
+ . '
' . $emptyLabel . '
'
. '
'
- . ' Добавить раздел '
+ . ' ' . $addLabel . ' '
. '
';
return $html;
@@ -2213,18 +2243,21 @@
protected static function catalogTokenHtml($fieldId, $catalogId, $name, $fields)
{
+ $removeLabel = AdminLocale::translateMarkup('Убрать раздел');
return ''
. ' '
. '' . self::e($name) . ' '
. ' '
- . ' '
+ . ' '
. ' ';
}
protected static function renderCatalogNodes(array $items, $fieldId, array $selected)
{
$html = '';
+ $documentLabel = AdminLocale::translateMarkup('документ #');
+ $hiddenLabel = AdminLocale::translateMarkup('скрыт');
foreach ($items as $item) {
$fields = implode(',', isset($item['fields_use']) ? $item['fields_use'] : array());
$isSelected = isset($selected[$item['id']]);
@@ -2232,8 +2265,8 @@
. ''
. '' . self::e($item['name']) . ' #' . (int) $item['id']
- . ($item['document_id'] > 0 ? ' · документ #' . (int) $item['document_id'] : '') . ' '
- . ((int) $item['status'] === 1 ? '' : 'скрыт ') . ' ';
+ . ($item['document_id'] > 0 ? ' · ' . $documentLabel . (int) $item['document_id'] : '') . ''
+ . ((int) $item['status'] === 1 ? '' : '' . $hiddenLabel . ' ') . '';
if (!empty($item['children'])) { $html .= '' . self::renderCatalogNodes($item['children'], $fieldId, $selected) . ' '; }
$html .= '';
}
diff --git a/adminx/modules/Documents/assets/documents.css b/adminx/modules/Documents/assets/documents.css
index f937d6c..d468d13 100644
--- a/adminx/modules/Documents/assets/documents.css
+++ b/adminx/modules/Documents/assets/documents.css
@@ -19,7 +19,6 @@
font-size: 20px;
line-height: 1.1;
font-weight: 800;
- font-variant-numeric: tabular-nums;
}
.documents-stat span {
font-size: 13px;
@@ -124,7 +123,6 @@
}
.documents-term-option .co-sub {
flex: 0 0 auto;
- font-variant-numeric: tabular-nums;
}
.documents-term-status {
display: flex;
@@ -512,26 +510,27 @@
width: 7ch;
}
.documents-col-title {
- width: 32%;
+ width: auto;
}
.documents-col-check {
width: 44px;
}
.documents-col-rubric {
- width: 22%;
+ width: 170px;
}
.documents-col-state {
- width: 18%;
+ width: 144px;
}
.documents-col-date {
- width: 18%;
+ width: 152px;
}
.documents-col-actions {
- width: 184px;
+ width: 164px;
}
.documents-name,
.documents-meta {
display: grid;
+ grid-template-columns: minmax(0, 1fr);
gap: 3px;
min-width: 0;
}
@@ -542,6 +541,15 @@
text-overflow: ellipsis;
white-space: nowrap;
}
+.documents-cell-title .documents-name b {
+ display: block;
+ max-width: 100%;
+ overflow: hidden;
+ overflow-wrap: anywhere;
+ text-overflow: clip;
+ white-space: normal;
+ line-height: 1.35;
+}
.documents-title-meta {
display: flex;
flex-wrap: wrap;
@@ -558,7 +566,10 @@
min-width: 0;
}
.documents-title-meta .mono {
+ overflow: hidden;
color: var(--text-muted);
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
.documents-search-flag {
display: inline-flex;
@@ -591,6 +602,9 @@
.documents-actions .btn {
text-decoration: none;
}
+.documents-cell-actions .documents-actions {
+ justify-content: flex-end;
+}
.documents-action-edit {
color: var(--blue-600);
}
@@ -1184,20 +1198,43 @@
.documents-edit-header h1 {
text-wrap: balance;
}
-.documents-editor-modes {
- flex: 0 0 auto;
+.documents-workspace-navigation {
+ margin-bottom: 16px;
}
-.documents-editor-modes .segmented-item {
- min-height: 36px;
- gap: 6px;
+.documents-workspace-tabs {
+ overflow: visible;
}
-.documents-edit-form[data-editor-mode="quick"] [data-editor-level="normal"],
-.documents-edit-form[data-editor-mode="quick"] [data-editor-level="advanced"],
-.documents-edit-form[data-editor-mode="normal"] [data-editor-level="advanced"] {
+.documents-workspace-tabs .tab {
+ min-height: 40px;
+}
+.documents-workspace-tabs .tab[aria-disabled="true"] {
+ opacity: 0.45;
+ cursor: not-allowed;
+}
+.documents-workspace-mobile {
display: none;
}
-.documents-edit-form[data-editor-mode="quick"] .documents-edit-layout {
- grid-template-columns: minmax(0, 1fr);
+.documents-editor-workspace [data-document-workspace-panel] {
+ display: none;
+}
+.documents-editor-workspace[data-active-panel="main"] [data-document-section="additional"],
+.documents-editor-workspace[data-active-panel="additional"] [data-document-section="main"] {
+ display: none;
+}
+.documents-editor-workspace[data-active-panel="attributes"] #documentForm,
+.documents-editor-workspace[data-active-panel="promotions"] #documentForm,
+.documents-editor-workspace[data-active-panel="variants"] #documentForm,
+.documents-editor-workspace[data-active-panel="shipping"] #documentForm {
+ display: none;
+}
+.documents-editor-workspace[data-active-panel="attributes"] [data-document-workspace-panel="attributes"],
+.documents-editor-workspace[data-active-panel="promotions"] [data-document-workspace-panel="promotions"],
+.documents-editor-workspace[data-active-panel="variants"] [data-document-workspace-panel="variants"],
+.documents-editor-workspace[data-active-panel="shipping"] [data-document-workspace-panel="shipping"] {
+ display: block;
+}
+.documents-editor-workspace [data-document-workspace-panel] {
+ margin-bottom: 0;
}
.documents-draft-recovery,
.documents-error-summary {
@@ -1358,9 +1395,6 @@
.documents-field-tabs .tab[hidden] {
display: none;
}
-.documents-field-tabs .tab-count {
- font-variant-numeric: tabular-nums;
-}
.documents-field-tabs .tab-hint {
margin-left: 2px;
font-size: 14px;
@@ -1602,7 +1636,6 @@
font-family: var(--font-mono, ui-monospace, "SFMono-Regular", monospace);
font-size: 11px;
font-weight: 500;
- font-variant-numeric: tabular-nums;
}
.ax-attr-type {
display: none;
@@ -1925,7 +1958,6 @@
font-size: 11px;
font-weight: 600;
color: var(--text-tertiary);
- font-variant-numeric: tabular-nums;
}
.field-count.is-over {
color: var(--red-600);
@@ -2029,7 +2061,6 @@
.documents-relation-empty {
color: var(--text-tertiary);
font-size: 12px;
- font-variant-numeric: tabular-nums;
}
.documents-relation-empty {
margin: 0;
@@ -2106,7 +2137,6 @@
white-space: nowrap;
color: var(--text-tertiary);
font-size: 11.5px;
- font-variant-numeric: tabular-nums;
}
.documents-relation-choice-caret {
flex: 0 0 auto;
@@ -2520,7 +2550,6 @@
.documents-relation-id {
font-family: var(--font-mono);
color: var(--text-secondary);
- font-variant-numeric: tabular-nums;
}
.documents-relation-item b,
.documents-relation-item small,
@@ -2787,7 +2816,6 @@
.documents-revision-field span {
color: var(--text-secondary);
font-size: 12px;
- font-variant-numeric: tabular-nums;
}
.documents-revision-field-name {
display: grid;
@@ -3024,6 +3052,12 @@
}
}
@media (max-width: 700px) {
+ .documents-workspace-tabs {
+ display: none;
+ }
+ .documents-workspace-mobile {
+ display: grid;
+ }
.documents-single-media-field.documents-media-card {
grid-template-columns: 1fr;
}
@@ -3116,7 +3150,7 @@
position: relative;
display: grid;
grid-template-columns: 28px minmax(0, 1fr) auto;
- grid-template-areas: "check title id" "check rubric state" "date date date" "actions actions actions";
+ grid-template-areas: "check title id" "check rubric rubric" "state state state" "date date date" "actions actions actions";
gap: 9px 10px;
padding: 13px 12px 11px;
border-bottom: 1px solid var(--border-default);
@@ -3135,6 +3169,7 @@
box-shadow: inset 3px 0 0 var(--red-500);
}
.documents-table .documents-row td {
+ width: auto;
min-width: 0;
padding: 0;
border: 0;
@@ -3527,3 +3562,62 @@
grid-template-columns: 1fr;
}
}
+.documents-relations-section {
+ margin-top: var(--space-5);
+}
+.documents-relations-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: var(--space-5);
+}
+.documents-relation-list {
+ display: grid;
+ align-content: start;
+ gap: var(--space-2);
+ min-width: 0;
+}
+.documents-relation-title,
+.documents-relation-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-3);
+}
+.documents-relation-title {
+ padding-bottom: var(--space-2);
+}
+.documents-relation-title span {
+ color: var(--text-secondary);
+ font-size: 12px;
+}
+.documents-relation-row {
+ min-height: 48px;
+ padding: var(--space-2) var(--space-3);
+ border-radius: var(--radius-sm);
+ background: var(--surface-muted);
+ color: var(--text-primary);
+ text-decoration: none;
+}
+.documents-relation-row:hover {
+ background: var(--blue-50);
+ color: var(--blue-700);
+}
+.documents-relation-row > span {
+ display: grid;
+ gap: 2px;
+ min-width: 0;
+}
+.documents-relation-row b,
+.documents-relation-row small {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.documents-relation-row small {
+ color: var(--text-secondary);
+}
+@media (max-width: 820px) {
+ .documents-relations-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/adminx/modules/Documents/assets/documents.js b/adminx/modules/Documents/assets/documents.js
index 15647b5..84d069d 100644
--- a/adminx/modules/Documents/assets/documents.js
+++ b/adminx/modules/Documents/assets/documents.js
@@ -22,7 +22,7 @@
currentRemarkDocumentId: 0,
draftTimer: null,
draftCandidate: null,
- editorMode: 'normal',
+ workspacePanel: 'main',
errorTargets: [],
conditionsInitialized: false,
@@ -67,8 +67,10 @@
var fieldTab = e.target.closest('[data-document-field-tab]');
if (fieldTab) { self.activateFieldGroup(fieldTab.getAttribute('data-document-field-tab')); }
- var editorMode = e.target.closest('[data-document-editor-mode]');
- if (editorMode) { self.setEditorMode(editorMode.getAttribute('data-document-editor-mode'), true); }
+ var workspaceTab = e.target.closest('[data-document-workspace-tab]');
+ if (workspaceTab && workspaceTab.getAttribute('aria-disabled') !== 'true') { self.setWorkspacePanel(workspaceTab.getAttribute('data-document-workspace-tab'), true); }
+ var workspaceOpen = e.target.closest('[data-document-workspace-open]');
+ if (workspaceOpen) { self.setWorkspacePanel(workspaceOpen.getAttribute('data-document-workspace-open'), true); }
if (e.target.closest('[data-document-draft-restore]')) { self.restoreLocalDraft(); }
if (e.target.closest('[data-document-draft-discard]')) { self.discardLocalDraft(); }
var errorJump = e.target.closest('[data-document-error-jump]');
@@ -201,7 +203,7 @@
this.updateAliasTemplateHint();
this.initTermInputs();
this.applyFieldConditions();
- this.initEditorMode();
+ this.initWorkspace();
this.setDirty(false);
window.setTimeout(function () { self.checkLocalDraft(); }, 180);
window.addEventListener('beforeunload', function (e) {
@@ -211,6 +213,28 @@
});
}
+ document.addEventListener('change', function (e) {
+ if (e.target.matches('[data-document-workspace-select]')) {
+ self.setWorkspacePanel(e.target.value, true);
+ }
+ });
+
+ document.addEventListener('keydown', function (e) {
+ var current = e.target.closest('[data-document-workspace-tab]');
+ if (!current || ['ArrowLeft', 'ArrowRight', 'Home', 'End'].indexOf(e.key) === -1) { return; }
+ var tabs = Array.prototype.slice.call(document.querySelectorAll('[data-document-workspace-tab]')).filter(function (tab) {
+ return tab.getAttribute('aria-disabled') !== 'true';
+ });
+ if (!tabs.length) { return; }
+ e.preventDefault();
+ var index = tabs.indexOf(current);
+ if (e.key === 'Home') { index = 0; }
+ else if (e.key === 'End') { index = tabs.length - 1; }
+ else if (e.key === 'ArrowLeft') { index = (index - 1 + tabs.length) % tabs.length; }
+ else { index = (index + 1) % tabs.length; }
+ self.setWorkspacePanel(tabs[index].getAttribute('data-document-workspace-tab'), true);
+ });
+
document.addEventListener('submit', function (e) {
var presetForm = e.target.closest('[data-document-preset-form]');
if (presetForm) {
@@ -376,7 +400,7 @@
document.addEventListener('keydown', function (e) {
if ((e.ctrlKey || e.metaKey) && String(e.key || '').toLowerCase() === 's' && self.form) {
e.preventDefault();
- self.submit(true);
+ self.saveActiveWorkspace();
}
});
@@ -736,6 +760,8 @@
applyFilters: function (form, push) {
if (!form) { return; }
+ clearTimeout(this.filterTimer);
+ this.filterTimer = null;
this.applyFilterUrl(this.filterUrl(form), push);
},
@@ -1399,33 +1425,76 @@
this.setSaveState(this.submitting ? 'saving' : (this.dirty ? 'dirty' : 'saved'));
},
- initEditorMode: function () {
- if (!this.form) { return; }
- var mode = this.form.getAttribute('data-quick-edit') === '1' ? 'quick' : 'normal';
- try { mode = window.localStorage.getItem(this.editorModeKey()) || mode; } catch (e) {}
- this.setEditorMode(mode, false);
+ initWorkspace: function () {
+ if (!this.form || !document.querySelector('[data-document-workspace]')) { return; }
+ var panel = 'main';
+ try { panel = window.localStorage.getItem(this.workspaceKey()) || panel; } catch (e) {}
+ this.setWorkspacePanel(panel, false);
},
- editorModeKey: function () {
+ workspaceKey: function () {
var actor = this.form ? this.form.getAttribute('data-actor-id') || '0' : '0';
var rubric = this.form ? this.form.getAttribute('data-rubric-id') || '0' : '0';
- return 'ave.adminx.document-editor-mode:' + actor + ':' + rubric;
+ var workspace = document.querySelector('[data-document-workspace]');
+ var kind = workspace && workspace.getAttribute('data-catalog-mode') === '1' ? 'product' : 'document';
+ return 'ave.adminx.document-workspace:' + actor + ':' + rubric + ':' + kind;
},
- setEditorMode: function (mode, persist) {
- if (!this.form) { return; }
- if (['quick', 'normal', 'advanced'].indexOf(mode) === -1) { mode = 'normal'; }
- this.editorMode = mode;
- this.form.setAttribute('data-editor-mode', mode);
- document.querySelectorAll('[data-document-editor-mode]').forEach(function (button) {
- var active = button.getAttribute('data-document-editor-mode') === mode;
- button.classList.toggle('is-active', active);
- button.setAttribute('aria-pressed', active ? 'true' : 'false');
- });
- if (persist) {
- try { window.localStorage.setItem(this.editorModeKey(), mode); } catch (e) {}
+ setWorkspacePanel: function (panel, persist) {
+ var workspace = document.querySelector('[data-document-workspace]');
+ if (!workspace) { return; }
+ var button = workspace.querySelector('[data-document-workspace-tab="' + panel + '"]');
+ if (!button || button.getAttribute('aria-disabled') === 'true') {
+ panel = 'main';
+ button = workspace.querySelector('[data-document-workspace-tab="main"]');
}
- window.setTimeout(function () { if (Adminx.CodeEditor) { Adminx.CodeEditor.refreshAll(); } }, 40);
+ this.workspacePanel = panel;
+ workspace.setAttribute('data-active-panel', panel);
+ workspace.querySelectorAll('[data-document-workspace-tab]').forEach(function (tab) {
+ var active = tab.getAttribute('data-document-workspace-tab') === panel;
+ tab.classList.toggle('is-active', active);
+ tab.setAttribute('aria-selected', active ? 'true' : 'false');
+ tab.setAttribute('tabindex', active ? '0' : '-1');
+ });
+ var select = workspace.querySelector('[data-document-workspace-select]');
+ if (select) { select.value = panel; }
+ if (persist) {
+ try { window.localStorage.setItem(this.workspaceKey(), panel); } catch (e) {}
+ }
+ window.setTimeout(function () {
+ if (Adminx.CodeEditor) { Adminx.CodeEditor.refreshAll(); }
+ if (persist && button && typeof button.focus === 'function' && document.activeElement !== select) {
+ button.focus({ preventScroll: true });
+ }
+ }, 40);
+ },
+
+ workspaceForTarget: function (target) {
+ if (!target) { return 'main'; }
+ var productPanel = target.closest('[data-document-workspace-panel]');
+ if (productPanel) { return productPanel.getAttribute('data-document-workspace-panel') || 'main'; }
+ var section = target.closest('[data-document-section]');
+ return section ? section.getAttribute('data-document-section') || 'main' : 'main';
+ },
+
+ setWorkspaceForTarget: function (target) {
+ this.setWorkspacePanel(this.workspaceForTarget(target), true);
+ },
+
+ saveActiveWorkspace: function () {
+ var workspace = document.querySelector('[data-document-workspace]');
+ var panel = workspace ? workspace.getAttribute('data-active-panel') || 'main' : 'main';
+ if (panel === 'attributes' || panel === 'shipping' || panel === 'promotions') {
+ var form = workspace.querySelector('[data-document-workspace-panel="' + panel + '"] form');
+ if (form && typeof form.requestSubmit === 'function') { form.requestSubmit(); }
+ else if (form) { form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true })); }
+ return;
+ }
+ if (panel === 'variants') {
+ if (Adminx.Toast) { Adminx.Toast.show('В этом разделе нет несохранённых полей', 'info'); }
+ return;
+ }
+ this.submit(true);
},
draftKey: function () {
@@ -1556,6 +1625,11 @@
promoteCreatedDocument: function (id, stayUrl) {
id = parseInt(id, 10) || 0;
if (!id) { return; }
+ var workspace = document.querySelector('[data-document-workspace]');
+ if (workspace && workspace.getAttribute('data-catalog-mode') === '1') {
+ window.location.href = stayUrl;
+ return;
+ }
window.history.replaceState({ adminxDocumentEdit: true }, '', stayUrl);
document.querySelectorAll('[data-document-remarks], [data-document-aliases], [data-document-revisions], [data-document-snapshot], [data-open-drawer="documentPresetDrawer"]').forEach(function (button) {
button.disabled = false;
@@ -2079,6 +2153,7 @@
if (jumpPanel !== null) { self.activateFieldGroup(jumpPanel); }
var firstInvalid = self.form.querySelector('.is-invalid');
if (firstInvalid) {
+ self.setWorkspaceForTarget(firstInvalid);
window.setTimeout(function () {
firstInvalid.scrollIntoView({ behavior: 'smooth', block: 'center' });
var input = firstInvalid.matches('input,select,textarea') ? firstInvalid : firstInvalid.querySelector('input,select,textarea,[contenteditable="true"]');
@@ -2118,12 +2193,7 @@
jumpToError: function (index) {
var target = this.errorTargets[parseInt(index, 10) || 0];
if (!target) { return; }
- var level = target.closest('[data-editor-level]');
- if (level) {
- var required = level.getAttribute('data-editor-level');
- if (required === 'advanced') { this.setEditorMode('advanced', true); }
- else if (required === 'normal' && this.editorMode === 'quick') { this.setEditorMode('normal', true); }
- }
+ this.setWorkspaceForTarget(target);
var panel = target.closest('[data-document-field-panel]');
if (panel) { this.activateFieldGroup(panel.getAttribute('data-document-field-panel')); }
window.setTimeout(function () {
diff --git a/adminx/modules/Documents/assets/redirects.js b/adminx/modules/Documents/assets/redirects.js
index 45654d4..51ecddd 100644
--- a/adminx/modules/Documents/assets/redirects.js
+++ b/adminx/modules/Documents/assets/redirects.js
@@ -6,7 +6,7 @@
filterTimer: null,
filterAbort: null,
filterRequest: 0,
- base: function () { return window.ADMINX_BASE || '/adminx'; },
+ base: function () { return window.ADMINX_BASE || Adminx.base(); },
csrf: function () { var el = document.querySelector('[data-redirect-csrf]'); return el ? el.value : ''; },
json: function (response) { return response.json().then(function (data) { if (!response.ok || data.success === false) { throw new Error(data.message || 'Ошибка запроса'); } return data; }); },
esc: function (value) { var div = document.createElement('div'); div.textContent = value == null ? '' : String(value); return div.innerHTML; },
@@ -47,6 +47,8 @@
load: function (url, push) {
var self = this;
+ clearTimeout(this.filterTimer);
+ this.filterTimer = null;
var requestId = ++this.filterRequest;
var active = document.activeElement;
var focusName = active && active.closest && active.closest('[data-redirect-filter]') ? active.getAttribute('name') : '';
diff --git a/adminx/modules/Documents/language/en/client.xml b/adminx/modules/Documents/language/en/client.xml
index c0d8925..1dd420f 100644
--- a/adminx/modules/Documents/language/en/client.xml
+++ b/adminx/modules/Documents/language/en/client.xml
@@ -1,47 +1,290 @@
+ x
Add more...
+ The address of the document will be written into the link field.
+ Up to 10 characters, optional.
+ Token copied
+ <button class="btn btn-ghost btn-icon btn-sm documents-media-remove" type="button" data-document-media-remove data-tooltip="Delete" aria-label="Delete"><i class="ti ti-trash"></i></button>
+ field
+ Clear all field lines?
+ Parameter
+ Rebuild JSON snapshots?
New
+ click to select
+ Failed to check alias
+ API token created
Add a keyword
+ <div class="modal-body"><div class="input-wrap documents-relation-search"><i class="ti ti-search"></i><input class="input" type="search" placeholder="ID, title or alias" data-relation-search></div><div class="documents-picker-status" data-relation-status>Loading...</div><div class="documents-relation-list" data-relation-list></div></div>
+ JSON snapshot
+ to
Creation preset
+ uh
+ Teaser ID/link
+ Internal server error. Please refresh the page and try again.
+ <button class="btn btn-ghost btn-icon btn-sm" type="button" data-document-media-down data-tooltip="Below" aria-label="Below"><i class="ti ti-arrow-down"></i></button>
+ "><button class="btn btn-secondary btn-icon btn-sm" type="button" data-document-media-pick data-tooltip="Select file" aria-label="Select file"><i class="ti ti-
+ ts
+ The document ID will be written in the field.
+ after saving
Creation preset saved
Failed to load list
+ h
+ h
+ <section class="documents-revision-system"><div class="documents-revision-subhead"><i class="ti ti-settings"></i><b>Basic settings</b><span>
+ [link]" data-media-key="link" value="" placeholder="Link or document" data-document-media-url data-document-picker-type="all">
+ Restore
+ <button class="btn btn-secondary btn-icon btn-sm" type="button" data-document-media-doc-pick data-tooltip="Select document" aria-label="Select document"><i class="ti ti-file-search"></i></button></div>
+ </span><label class="documents-revision-group-check"><input type="checkbox" data-document-revision-group="field" checked><span>All</span></label></div>
+ ID, name or alias
+ Select file
+ Column 3
+ Redirect saved
+ The secret is ready to be copied.
+ The server returned an incorrect response.
+ Refresh page
+ Untitled
+ JSON snapshots reassembled:
+ <button class="btn btn-ghost btn-icon btn-sm" type="button" data-document-media-up data-tooltip="Above" aria-label="Above"><i class="ti ti-arrow-up"></i></button>
+ Document ID
+ Note added
+ new
new
+ Draft
+ Title
+ Revision deleted
+ Document title
+ Files uploaded
+ Select revision
+ Rebuild JSON
Clear daily statistics?
+ Column 1
+ fields
+ th
+ There are no matching files in the folder
Statistics cleared
+ not created yet
+ Copy
+ Redirect removed
+ Audits
+ I
+ l
+ a
+ b
+ Document saved
+ <span class="badge badge-gray">not created</span>
+ Failed to read folder
+ Note deleted
+ File not selected
+ f
+ o
+ <div><dt>File</dt><dd class="mono">
+ kg
+ r
+ . The window must be left open until completion.
in the section
+ s
All view_count lines will be removed. General document counters will remain unchanged.
+ <button class="btn btn-secondary btn-icon btn-sm" type="button" data-document-media-pick data-tooltip="Select file" aria-label="Select file"><i class="ti ti-paperclip"></i></button>
+ Rebuild
+ <span class="badge badge-amber">needs reassembly</span>
doc.
+ 0 B
+ d
<div class="documents-term-status is-error"><i class="ti ti-alert-circle"></i><span>Failed to load options</span></div>
+ <div class="empty-state">There are no revisions yet. The first snapshot will appear after saving the document.</div>
+ <div><label class="documents-revision-check" aria-label="Restore
+ Clear the field?
+ Column 2
+ Alias can be left empty
+ e
+ Length
+ f
Delete "
+ Withdrawn
+ </p></div><button class="modal-close" type="button" data-relation-close aria-label="Close"><i class="ti ti-x"></i></button></div>
+ <span class="badge badge-green">relevant</span>
URL copied
There are no saved values yet
+ [description]" data-media-key="description" rows="2" placeholder="Description"></textarea>
+ <button class="btn btn-ghost btn-icon btn-sm" type="button" data-document-value-down data-tooltip="Below" aria-label="Below"><i class="ti ti-arrow-down"></i></button>
+ The integration will immediately lose access. It will be impossible to return this token.
+ Errors found:
+ Category template: /
+ , errors:
+ Template
Failed to load statistics
+ API token revoked
Find or add a tag
+ n
+ Open the desired folder and confirm your selection. All suitable files from it will be added to the field.
+ <div><dt>State</dt><dd>
+ <div class="empty-state">Loading...</div>
+ The contents of the snapshot will appear after selecting a revision.
Delete
+ Copied
+ optional
+ The description will appear after filling out the meta description.
+ Documents
+ The document has been restored
+ g
No matches
+ <button class="btn btn-ghost btn-icon btn-sm" type="button" data-document-value-up data-tooltip="Above" aria-label="Above"><i class="ti ti-arrow-up"></i></button>
+ Permissions for one external integration.
+ The document has changed
Request Error
+ fields
Clear
+ Revoke
+ Changes saved
+ [title]" data-media-key="title" value="" placeholder="Image title">
+ Remove
+ n
+ Added from folder:
+ Edit
+ Failed to load revisions
+ Meaning
+ Select document
+ <div><dt>Formed by</dt><dd>
+ Fields only
+ b
+ The category does not have a path template: the document alias is used from the root of the site.
+ Failed to generate alias
+ and
+ Height,
+ No image selected
+ <div><dt>Size</dt><dd>
+ JSON snapshot rebuilt
+ Document revisions
+ Heading
Loading...
+ Remove document
+ Add from this folder
+ All lines in this field will be removed from the document after saving.
+ <button class="btn btn-ghost btn-icon btn-sm documents-drag-handle" type="button" data-doc-drag draggable="true" data-tooltip="Drag" aria-label="Drag"><i class="ti ti-grip-vertical"></i></button>
Preset deleted
+ Pictures
+ / + document alias. The date is taken from the publication.
+ <div><dt>Fields</dt><dd class="mono">
+ Batch rebuild stopped
+ <article class="documents-revision-field documents-revision-system-field"><div><label class="documents-revision-check" aria-label="Restore
+ Failed to get snapshot status
+ with
+ These files are already in the field
+ The pictures will be sequentially reassembled to
+ pictures
+ yu
+ [name]" data-media-key="name" value="" placeholder="File name">
+ Revoke an API token?
+ <section class="documents-revision-content"><div class="documents-revision-subhead"><i class="ti ti-forms"></i><b>Category fields</b><span>
+ e
from
+ m
+ w
Failed to apply filters
+ Field
Read only
Click to find document
Add "
<div class="documents-term-status"><i class="ti ti-loader-2"></i><span>Looking for matches...</span></div>
preset
Delete preset
+ Failed to generate short alias
+ can be left blank
+ Select at least one resolution
+ Width,
+ Delete revision
+ <div><dt>Status</dt><dd>Loading...</dd></div>
+ All elements of this field will be removed from the document after saving.
+ Weight,
+ History of field values
+ Additionally
+ at
+ Failed to check short alias
+ Checking...
+ draft
Assignment document
+ Add files from a folder
+ Revisions deleted
+ cm
+ <button class="btn btn-ghost btn-icon btn-sm documents-value-remove" type="button" data-document-value-remove data-tooltip="Delete" aria-label="Delete"><i class="ti ti-trash"></i></button>
+ ъ
+ t
+ </span><label class="documents-revision-group-check"><input type="checkbox" data-document-revision-group="document" checked><span>All</span></label></div>
+ The document has already been changed. Refresh the page.
+ <button class="documents-media-thumb" type="button" data-document-media-pick aria-label="Select file"><span><i class="ti ti-
+ The story is still empty
Incorrect answer
+ Clear all field elements?
Find or add a keyword
in
+ Stay
+ <div class="modal-footer"><div class="mf-left documents-picker-count" data-relation-count></div><button class="btn btn-ghost" type="button" data-relation-close>Close</button></div>
No document selected
New redirect
+ Old URL
+ <div class="modal-header"><span class="dialog-icon info"><i class="ti ti-file-search"></i></span><div style="flex:1"><h3>Select document</h3><p class="text-secondary" style="margin-top:4px">
Add a tag
+ The path will be written in the document field.
+ A newer version is already saved on the server. Refresh the page, check the changes, and save the document again.
+ sch
+ n
+ Title
+ t
+ ъ
+ th
+ d
+ Fields only
+ e
+ h
+ e
+ Note added
+ n
+ yu
+ h
+ s
+ JSON snapshot rebuilt
+ w
+ o
+ to
+ not created yet
+ cm
+ The document has been restored
+ API token revoked
+ Draft
+ Revisions deleted
+ r
+ f
+ ts
+ m
+ a
+ Field #
+ Note deleted
+ b
+ I
+ Document saved
+ with
+ g
+ Revision deleted
in
+ b
+ x
+ sch
+ at
+ can be left blank
+ kg
Creation preset saved
+ B
+ Redirect removed
+ l
Preset deleted
+ f
Document #
+ Redirect saved
+ API token created
+ uh
+ and
+ after saving
New
+ Template
diff --git a/adminx/modules/Documents/language/en/zz-interface.xml b/adminx/modules/Documents/language/en/zz-interface.xml
new file mode 100644
index 0000000..637036a
--- /dev/null
+++ b/adminx/modules/Documents/language/en/zz-interface.xml
@@ -0,0 +1,9 @@
+
+
+ No sections selected.
+ Add section
+ Find section
+ Remove section
+ document #
+ hidden
+
diff --git a/adminx/modules/Documents/language/ru/client.xml b/adminx/modules/Documents/language/ru/client.xml
index 9f4a554..0fe7fc6 100644
--- a/adminx/modules/Documents/language/ru/client.xml
+++ b/adminx/modules/Documents/language/ru/client.xml
@@ -1,47 +1,290 @@
+ х
Добавить ещё…
+ В поле-ссылку будет записан адрес документа.
+ До 10 символов, необязательно.
+ Токен скопирован
+ <button class="btn btn-ghost btn-icon btn-sm documents-media-remove" type="button" data-document-media-remove data-tooltip="Удалить" aria-label="Удалить"><i class="ti ti-trash"></i></button>
+ поле
+ Очистить все строки поля?
+ Параметр
+ Пересобрать JSON-снимки?
Новый
+ нажмите, чтобы выбрать
+ Не удалось проверить alias
+ API-токен создан
Добавить ключевое слово
+ <div class="modal-body"><div class="input-wrap documents-relation-search"><i class="ti ti-search"></i><input class="input" type="search" placeholder="ID, название или alias" data-relation-search></div><div class="documents-picker-status" data-relation-status>Загрузка...</div><div class="documents-relation-list" data-relation-list></div></div>
+ JSON-снимок
+ к
Пресет создания
+ э
+ ID тизера / ссылка
+ Внутренняя ошибка сервера. Обновите страницу и повторите попытку.
+ <button class="btn btn-ghost btn-icon btn-sm" type="button" data-document-media-down data-tooltip="Ниже" aria-label="Ниже"><i class="ti ti-arrow-down"></i></button>
+ "><button class="btn btn-secondary btn-icon btn-sm" type="button" data-document-media-pick data-tooltip="Выбрать файл" aria-label="Выбрать файл"><i class="ti ti-
+ ц
+ В поле будет записан ID документа.
+ после сохранения
Пресет создания сохранён
Не удалось загрузить список
+ ч
+ з
+ <section class="documents-revision-system"><div class="documents-revision-subhead"><i class="ti ti-settings"></i><b>Основные настройки</b><span>
+ [link]" data-media-key="link" value="" placeholder="Ссылка или документ" data-document-media-url data-document-picker-type="all">
+ Восстановить
+ <button class="btn btn-secondary btn-icon btn-sm" type="button" data-document-media-doc-pick data-tooltip="Выбрать документ" aria-label="Выбрать документ"><i class="ti ti-file-search"></i></button></div>
+ </span><label class="documents-revision-group-check"><input type="checkbox" data-document-revision-group="field" checked><span>Все</span></label></div>
+ ID, название или alias
+ Выбрать файл
+ Колонка 3
+ Редирект сохранён
+ Секрет готов к копированию.
+ Сервер вернул некорректный ответ.
+ Обновить страницу
+ Без названия
+ JSON-снимки пересобраны:
+ <button class="btn btn-ghost btn-icon btn-sm" type="button" data-document-media-up data-tooltip="Выше" aria-label="Выше"><i class="ti ti-arrow-up"></i></button>
+ ID документа
+ Заметка добавлена
+ новый
новое
+ Черновик
+ Название
+ Ревизия удалена
+ Заголовок документа
+ Файлы загружены
+ Выберите ревизию
+ Пересобрать JSON
Очистить подневную статистику?
+ Колонка 1
+ поля
+ й
+ В папке нет подходящих файлов
Статистика очищена
+ ещё не создан
+ Копировать
+ Редирект удалён
+ Ревизии
+ я
+ л
+ а
+ ь
+ Документ сохранён
+ <span class="badge badge-gray">не создан</span>
+ Не удалось прочитать папку
+ Заметка удалена
+ Файл не выбран
+ ф
+ о
+ <div><dt>Файл</dt><dd class="mono">
+ кг
+ р
+ . Окно нужно оставить открытым до завершения.
в рубрике
+ ы
Все строки view_count будут удалены. Общие счётчики документов останутся без изменений.
+ <button class="btn btn-secondary btn-icon btn-sm" type="button" data-document-media-pick data-tooltip="Выбрать файл" aria-label="Выбрать файл"><i class="ti ti-paperclip"></i></button>
+ Пересобрать
+ <span class="badge badge-amber">нужна пересборка</span>
док.
+ 0 Б
+ д
<div class="documents-term-status is-error"><i class="ti ti-alert-circle"></i><span>Не удалось загрузить варианты</span></div>
+ <div class="empty-state">Ревизий пока нет. Первый снимок появится после сохранения документа.</div>
+ <div><label class="documents-revision-check" aria-label="Восстановить
+ Очистить поле?
+ Колонка 2
+ Alias можно оставить пустым
+ ё
+ Длина,
+ ж
Удалить «
+ Отозван
+ </p></div><button class="modal-close" type="button" data-relation-close aria-label="Закрыть"><i class="ti ti-x"></i></button></div>
+ <span class="badge badge-green">актуален</span>
URL скопирован
Сохранённых значений пока нет
+ [description]" data-media-key="description" rows="2" placeholder="Описание"></textarea>
+ <button class="btn btn-ghost btn-icon btn-sm" type="button" data-document-value-down data-tooltip="Ниже" aria-label="Ниже"><i class="ti ti-arrow-down"></i></button>
+ Интеграция сразу потеряет доступ. Вернуть этот токен будет невозможно.
+ Найдено ошибок:
+ Шаблон рубрики: /
+ , ошибок:
+ Шаблон
Не удалось загрузить статистику
+ API-токен отозван
Найти или добавить тег
+ н
+ Откройте нужную папку и подтвердите выбор. В поле добавятся все подходящие файлы из неё.
+ <div><dt>Состояние</dt><dd>
+ <div class="empty-state">Загрузка...</div>
+ Содержимое снимка появится после выбора ревизии.
Удалить
+ Скопировано
+ необязательно
+ Описание появится после заполнения meta description.
+ Документов
+ Документ восстановлен
+ г
Совпадений нет
+ <button class="btn btn-ghost btn-icon btn-sm" type="button" data-document-value-up data-tooltip="Выше" aria-label="Выше"><i class="ti ti-arrow-up"></i></button>
+ Права доступа для одной внешней интеграции.
+ Документ изменился
Ошибка запроса
+ полей
Очистить
+ Отозвать
+ Изменения сохранены
+ [title]" data-media-key="title" value="" placeholder="Заголовок изображения">
+ Убрать
+ п
+ Добавлено из папки:
+ Изменить
+ Не удалось загрузить ревизии
+ Значение
+ Выбрать документ
+ <div><dt>Сформирован</dt><dd>
+ Только поля
+ б
+ У рубрики нет шаблона пути: alias документа используется от корня сайта.
+ Не удалось сгенерировать alias
+ и
+ Высота,
+ Изображение не выбрано
+ <div><dt>Размер</dt><dd>
+ JSON-снимок пересобран
+ Ревизии документа
+ Заголовок
Загрузка...
+ Убрать документ
+ Добавить из этой папки
+ Все строки этого поля будут удалены из документа после сохранения.
+ <button class="btn btn-ghost btn-icon btn-sm documents-drag-handle" type="button" data-doc-drag draggable="true" data-tooltip="Перетащить" aria-label="Перетащить"><i class="ti ti-grip-vertical"></i></button>
Пресет удалён
+ Снимки
+ / + alias документа. Дата берётся из публикации.
+ <div><dt>Поля</dt><dd class="mono">
+ Пакетная пересборка остановлена
+ <article class="documents-revision-field documents-revision-system-field"><div><label class="documents-revision-check" aria-label="Восстановить
+ Не удалось получить статус снимка
+ с
+ Эти файлы уже есть в поле
+ Снимки будут последовательно пересобраны для
+ снимков
+ ю
+ [name]" data-media-key="name" value="" placeholder="Название файла">
+ Отозвать API-токен?
+ <section class="documents-revision-content"><div class="documents-revision-subhead"><i class="ti ti-forms"></i><b>Поля рубрики</b><span>
+ е
из
+ м
+ ш
Не удалось применить фильтры
+ Поле
Только чтение
Нажмите, чтобы найти документ
Добавить «
<div class="documents-term-status"><i class="ti ti-loader-2"></i><span>Ищем совпадения…</span></div>
пресет
Удалить пресет
+ Не удалось сгенерировать короткий алиас
+ можно оставить пустым
+ Выберите хотя бы одно разрешение
+ Ширина,
+ Удалить ревизию
+ <div><dt>Состояние</dt><dd>Загрузка...</dd></div>
+ Все элементы этого поля будут удалены из документа после сохранения.
+ Вес,
+ История значений полей
+ Дополнительно
+ у
+ Не удалось проверить короткий alias
+ Проверка...
+ черновик
Документ назначения
+ Добавить файлы из папки
+ Ревизии удалены
+ см
+ <button class="btn btn-ghost btn-icon btn-sm documents-value-remove" type="button" data-document-value-remove data-tooltip="Удалить" aria-label="Удалить"><i class="ti ti-trash"></i></button>
+ ъ
+ т
+ </span><label class="documents-revision-group-check"><input type="checkbox" data-document-revision-group="document" checked><span>Все</span></label></div>
+ Документ уже изменён. Обновите страницу.
+ <button class="documents-media-thumb" type="button" data-document-media-pick aria-label="Выбрать файл"><span><i class="ti ti-
+ История пока пустая
Некорректный ответ
+ Очистить все элементы поля?
Найти или добавить ключевое слово
в
+ Остаться
+ <div class="modal-footer"><div class="mf-left documents-picker-count" data-relation-count></div><button class="btn btn-ghost" type="button" data-relation-close>Закрыть</button></div>
Документ не выбран
Новый редирект
+ Старый URL
+ <div class="modal-header"><span class="dialog-icon info"><i class="ti ti-file-search"></i></span><div style="flex:1"><h3>Выбрать документ</h3><p class="text-secondary" style="margin-top:4px">
Добавить тег
+ Путь будет записан в поле документа.
+ На сервере уже сохранена более новая версия. Обновите страницу, проверьте изменения и сохраните документ повторно.
+ щ
+ н
+ Название
+ т
+ ъ
+ й
+ д
+ Только поля
+ е
+ з
+ ё
+ Заметка добавлена
+ п
+ ю
+ ч
+ ы
+ JSON-снимок пересобран
+ ш
+ о
+ к
+ ещё не создан
+ см
+ Документ восстановлен
+ API-токен отозван
+ Черновик
+ Ревизии удалены
+ р
+ ф
+ ц
+ м
+ а
+ Поле #
+ Заметка удалена
+ б
+ я
+ Документ сохранён
+ с
+ г
+ Ревизия удалена
в
+ ь
+ х
+ щ
+ у
+ можно оставить пустым
+ кг
Пресет создания сохранён
+ Б
+ Редирект удалён
+ л
Пресет удалён
+ ж
Документ #
+ Редирект сохранён
+ API-токен создан
+ э
+ и
+ после сохранения
Новый
+ Шаблон
diff --git a/adminx/modules/Documents/language/ru/zz-interface.xml b/adminx/modules/Documents/language/ru/zz-interface.xml
new file mode 100644
index 0000000..3c27252
--- /dev/null
+++ b/adminx/modules/Documents/language/ru/zz-interface.xml
@@ -0,0 +1,9 @@
+
+
+ Разделы не выбраны.
+ Добавить раздел
+ Найти раздел
+ Убрать раздел
+ документ #
+ скрыт
+
diff --git a/adminx/modules/Documents/view/_product_panels.twig b/adminx/modules/Documents/view/_product_panels.twig
new file mode 100644
index 0000000..08368c2
--- /dev/null
+++ b/adminx/modules/Documents/view/_product_panels.twig
@@ -0,0 +1,225 @@
+{% if catalog_mode and not is_new %}
+
+{% endif %}
+
+{% if catalog_mode and not is_new %}
+
+
Варианты товара {% if variant_group %}Группа #{{ variant_group.id }} {% endif %}
+
+ {% if variant_group %}
+
{{ variant_group.title }} {{ variant_group.is_primary ? 'Основной вариант группы' : 'Вариант группы' }}
+
Управление вариантами
+ {% else %}
+
Товар пока не объединён с вариантами Создайте группу на основе текущего товара.
+
+
+
+ Создать группу вариантов
+
+ {% endif %}
+
+
+{% endif %}
+
+{% if catalog_mode and not is_new %}
+
+ {% if native_attributes.attributes_count %}
+
+
+
+
+ {% for group in native_attributes.groups %}
+
+ {{ group.name }} {{ group.set_name }}
{{ group.items|length }}
+
+
+ {% endfor %}
+
+
+
+ {% else %}
+
+
+
+
+
Характеристик пока нет Назначьте набор характеристик разделу каталога.
+
Настроить наборы
+
+
+ {% endif %}
+
+{% endif %}
+
+{% if catalog_mode and not is_new %}
+
+{% endif %}
diff --git a/adminx/modules/Documents/view/edit.twig b/adminx/modules/Documents/view/edit.twig
index d3ad6a2..e187653 100644
--- a/adminx/modules/Documents/view/edit.twig
+++ b/adminx/modules/Documents/view/edit.twig
@@ -1,5 +1,5 @@
{% extends '@adminx/main.twig' %}
-{% block title %}{{ catalog_mode ? 'Товар #' ~ document.Id : (is_new ? 'Новый документ' : 'Документ #' ~ document.Id) }}{% endblock %}
+{% block title %}{{ catalog_mode ? (is_new ? 'Новый товар' : 'Товар #' ~ document.Id) : (is_new ? 'Новый документ' : 'Документ #' ~ document.Id) }}{% endblock %}
{% block content %}
@@ -11,72 +11,61 @@
-{% if catalog_mode and not is_new %}
Варианты товара {% if variant_group %}Группа #{{ variant_group.id }} {% endif %}{% if variant_group %}
{{ variant_group.title }} {{ variant_group.is_primary ? 'Основной вариант группы' : 'Вариант группы' }}
Управление вариантами{% else %}
Товар пока не объединён с вариантами Создайте группу на основе текущего товара.
Создать группу вариантов{% endif %}
{% endif %}
-
-{% if catalog_mode and not is_new and native_attributes.attributes_count %}
-
- Нативные характеристики Новый контур проверки. Публичная карточка пока использует старые поля.
{{ native_attributes.attributes_count }}
- {% for group in native_attributes.groups %}
{% endfor %}
-
-{% endif %}
-
-{% if catalog_mode and not is_new %}
-
-
-
-
-
-
Доставка и упаковка Физические грузовые места для расчёта перевозчиками.
-
Рассчитывать доставку
+
+
-
+ {% if catalog_mode and not is_new and product_readiness.items %}
+
+
+
+ Готовность товара {{ product_readiness.attention ? product_readiness.attention ~ ' раздела требуют внимания' : 'Обязательные данные заполнены' }}
+
+
+ {% for item in product_readiness.items %}
+
+
+ {{ item.title }} {{ item.label }}
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+
@@ -101,7 +90,7 @@
{% endif %}
-
+
@@ -172,7 +161,7 @@
-
+
@@ -231,7 +220,7 @@
-
+
@@ -294,7 +283,7 @@
-
+
@@ -321,7 +310,7 @@
-
+
@@ -339,7 +328,7 @@
-
+
@@ -394,6 +383,42 @@
+ {% if not is_new %}
+
+
+
+
+
Связи документов Прямые и обратные связи из полей документов.
+
+
{{ document_relations.outgoing|length + document_relations.incoming|length }}
+
+
+
+ {% endif %}
+
@@ -407,6 +432,9 @@
+{% include '@documents/_product_panels.twig' %}
+
+
{% if can_manage %}
diff --git a/adminx/modules/Events/Controller.php b/adminx/modules/Events/Controller.php
index c4a8738..3be4d82 100644
--- a/adminx/modules/Events/Controller.php
+++ b/adminx/modules/Events/Controller.php
@@ -33,12 +33,13 @@
$q = Request::getStr('q', '');
$limit = Request::getInt('limit', 300);
$sourceDef = Model::source($source);
+ $rows = Model::rows($sourceDef['code'], $q, $limit);
return $this->render('@events/index.twig', array(
'sources' => Model::summaries(),
'active_source' => $sourceDef['code'],
'active_source_def' => $sourceDef,
- 'rows' => Model::rows($sourceDef['code'], $q, $limit),
+ 'rows' => $rows,
'q' => $q,
'limit' => $limit,
'can_manage' => Permission::check('manage_events'),
@@ -56,7 +57,7 @@
return $this->error('Этот источник нельзя очистить', array(), 422);
}
- return $this->success('Журнал очищен', array(
+ return $this->success($source === 'audit' ? 'Аудит очищен' : 'Журнал очищен', array(
'redirect' => $this->base() . '/events?source=' . rawurlencode($source),
));
}
diff --git a/adminx/modules/Events/Model.php b/adminx/modules/Events/Model.php
index 796c67d..b7c4c84 100644
--- a/adminx/modules/Events/Model.php
+++ b/adminx/modules/Events/Model.php
@@ -29,7 +29,7 @@
'audit' => array(
'code' => 'audit',
'label' => 'Аудит',
- 'description' => 'Административные действия в панели управления.',
+ 'description' => 'История административных действий с пользователями, объектами и техническими деталями.',
'icon' => 'ti ti-shield-check',
'tile_bg' => 'var(--blue-100)',
'tile_fg' => 'var(--blue-600)',
@@ -155,7 +155,7 @@
AuditLog::record('events.audit_cleared', array(
'actor_id' => (int) $actorId > 0 ? (int) $actorId : null,
'target_type' => 'audit_log',
- 'meta' => array('deleted_rows' => $count),
+ 'meta' => array('deleted_rows' => $count, 'source' => $source['code']),
));
return true;
}
@@ -226,20 +226,24 @@
{
$rows = DB::query(
'SELECT * FROM ' . AuditLog::table() . ' ORDER BY created_at DESC, id DESC LIMIT ' . (int) $limit
- )->getAll();
+ )->getAll() ?: array();
$out = array();
foreach ($rows as $row) {
$meta = self::decodeMeta(isset($row['meta']) ? $row['meta'] : '');
$state = self::auditState((string) $row['action']);
+ $timestamp = strtotime((string) $row['created_at']);
$out[] = array(
'id' => (int) $row['id'],
'source' => 'audit',
'level' => $state,
- 'time' => strtotime($row['created_at']),
- 'time_display' => self::formatTime(strtotime($row['created_at'])),
+ 'time' => $timestamp,
+ 'time_display' => self::formatTime($timestamp),
+ 'date_group' => self::dateGroup($timestamp),
'ip' => (string) $row['ip'],
- 'actor' => $row['actor_name'] !== '' ? $row['actor_name'] : ('#' . (int) $row['actor_id']),
+ 'actor' => (string) $row['actor_name'] !== ''
+ ? (string) $row['actor_name']
+ : '#' . (int) $row['actor_id'],
'message' => AuditLog::actionLabel($row['action']),
'url' => trim((string) $row['target_type'] . ($row['target_id'] ? ' #' . $row['target_id'] : '')),
'details' => $meta,
@@ -250,6 +254,24 @@
return $out;
}
+ protected static function dateGroup($timestamp)
+ {
+ $timestamp = (int) $timestamp;
+ if ($timestamp <= 0) {
+ return 'Без даты';
+ }
+
+ if (date('Y-m-d', $timestamp) === date('Y-m-d')) {
+ return 'Сегодня';
+ }
+
+ if (date('Y-m-d', $timestamp) === date('Y-m-d', strtotime('-1 day'))) {
+ return 'Вчера';
+ }
+
+ return date('d.m.Y', $timestamp);
+ }
+
protected static function legacyRows($source, $limit)
{
$rows = self::readCsv($source, $limit);
diff --git a/adminx/modules/Events/assets/events.css b/adminx/modules/Events/assets/events.css
index a963b70..415c192 100644
--- a/adminx/modules/Events/assets/events.css
+++ b/adminx/modules/Events/assets/events.css
@@ -169,7 +169,6 @@
.events-hit-count {
min-width: 40px;
justify-content: center;
- font-variant-numeric: tabular-nums;
}
.events-message {
font-weight: 600;
@@ -234,6 +233,19 @@
border: none;
min-height: 260px;
}
+.events-table-day td {
+ padding: 9px 14px;
+ background: var(--background-muted);
+ color: var(--text-secondary);
+ font-size: 11px;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+.events-table-day span {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+}
@media (max-width: 760px) {
.events-table-wrap {
max-height: none;
diff --git a/adminx/modules/Events/assets/events.js b/adminx/modules/Events/assets/events.js
index 455fc61..142c972 100644
--- a/adminx/modules/Events/assets/events.js
+++ b/adminx/modules/Events/assets/events.js
@@ -1,5 +1,5 @@
/**
- * JS раздела «Системные события»: раскрытие деталей и очистка legacy-логов.
+ * JS раздела «Системные события»: раскрытие деталей и очистка журналов.
*/
(function (window, document) {
'use strict';
@@ -45,8 +45,10 @@
var self = this;
Adminx.Confirm.open({
kind: 'error',
- title: 'Очистить журнал?',
- message: 'Все записи выбранного журнала будут удалены. В аудите останется только запись о самой очистке.',
+ title: source === 'audit' ? 'Очистить аудит?' : 'Очистить журнал?',
+ message: source === 'audit'
+ ? 'Все записи аудита будут удалены. Останется только служебная запись о самой очистке.'
+ : 'Все записи выбранного журнала будут удалены. В аудите останется запись о самой очистке.',
confirmLabel: 'Очистить',
confirmClass: 'btn-danger',
onConfirm: function () {
diff --git a/adminx/modules/Events/language/en/client.xml b/adminx/modules/Events/language/en/client.xml
index 7ea7d96..50eae72 100644
--- a/adminx/modules/Events/language/en/client.xml
+++ b/adminx/modules/Events/language/en/client.xml
@@ -3,7 +3,6 @@
Error
Clear log?
Clear
- All entries in the selected journal will be deleted. The audit will only contain a record of the cleaning itself.
Network error
Error
diff --git a/adminx/modules/Events/language/ru/client.xml b/adminx/modules/Events/language/ru/client.xml
index 11b11a9..fac0440 100644
--- a/adminx/modules/Events/language/ru/client.xml
+++ b/adminx/modules/Events/language/ru/client.xml
@@ -3,7 +3,6 @@
Ошибка
Очистить журнал?
Очистить
- Все записи выбранного журнала будут удалены. В аудите останется только запись о самой очистке.
Ошибка сети
Ошибка
diff --git a/adminx/modules/Events/view/index.twig b/adminx/modules/Events/view/index.twig
index 2cfbd8e..73bbecb 100644
--- a/adminx/modules/Events/view/index.twig
+++ b/adminx/modules/Events/view/index.twig
@@ -45,7 +45,7 @@
{% if active_source == 'legacy' %}
-Что попадает в журнал приложения Сюда выводятся служебные сообщения публичного runtime: результаты фоновых операций, предупреждения и записи старых интеграций. Действия администраторов находятся во вкладке «Аудит», SQL и 404 вынесены отдельно.
+Что попадает в журнал приложения Сюда выводятся служебные сообщения публичного runtime: результаты фоновых операций, предупреждения и записи старых интеграций. Действия администраторов находятся в «Аудите», SQL и 404 вынесены отдельно.
{% endif %}
@@ -78,8 +78,8 @@
-
{{ active_source == 'referrers' ? 'Журнал переходов' : 'Список событий' }}
-
{{ active_source == 'referrers' ? 'Сгруппированные внешние входы, источники и рекламные метки.' : 'Последние записи выбранного источника с фильтром и деталями.' }}
+
{{ active_source == 'audit' ? 'Аудит действий' : (active_source == 'referrers' ? 'Журнал переходов' : 'Список событий') }}
+
{{ active_source == 'audit' ? 'Кто, когда и что изменил в системе.' : (active_source == 'referrers' ? 'Сгруппированные внешние входы, источники и рекламные метки.' : 'Последние записи выбранного источника с фильтром и деталями.') }}