Repository navigation
Sync 3.5 to 3.6 - #2577
Sync 3.5 to 3.6#2577
Conversation
…ns-alignment Fix action buttons alignment
The shared-root mapping read KCFinder's typeURL, which resolves a relative rb_base_url against the browser script (or gives up when DOCUMENT_ROOT is a symlink), so it never named the folder: restricted managers got an empty, read-only browser on shared storage. Use the configured URL instead. A type root the site serves from outside a narrowed filemanager_path carries no file groups again instead of refusing every action. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…managers Links below a file manager root are made by the operator, never by the CMS: a shared upload folder, a plugin checked out elsewhere, images from another disk. A link whose real target exists and is new (not inside or around the ACL root, not a loop, not inside anything the walk already named or a shared type folder) is now a folder or file like any other, named by its own path, with every rule applied by that name: protected folders, file groups, the executable-name rule and the media browser type folder. Links that dangle, loop or would give a file a second name stay inert entries, and `..` past a link is refused. Both managers key a shared type folder the same way, so the classic manager can now enter it and edit its groups; the link that stands for a type folder is never deleted, renamed or unlinked. Removing any other passable link removes the link only and keeps its groups. A new fixture builds the media browser the way a web request does ($_SERVER, the working directory of browse.php, config.php), through a public_html -> evolution -> release chain, which is how the typeURL bug slipped through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ough a linked file A passed link keeps its own name, which file groups are keyed by, but the protected-folder rule compared only that name. A link to a folder protected outside the ACL root (an external view path; core/, manager/ or views/ under a narrowed filemanager_path) opened it to managers without the permission. fileManagerPathIsProtected() and fileManagerPathTouchesProtected() now also match where a path lands: the real path of its nearest existing ancestor with the rest appended, so new destinations are covered too. Removing a link resolves only the folder it sits in. textsave() checked a linked file by its name while file_put_contents() followed the link, so a manager without code permissions could write PHP into the target through a text name. It now refuses a final link through fileManagerIsSafeWriteTarget(), as the classic upload over an existing name does now too (a move across file systems copies, and the copy follows a link). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sions-for-shared-storage-3.5 fix(file-manager): shared storage and operator links in both file managers (3.5.x)
(cherry picked from commit 8e43927) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r-eof-3.5 fix(installer): stop CLI input loops on EOF
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The update widget writes update.php to the site root and opens it, and the catch-all of ng.inx answered 404, so the update could not start on nginx. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ames its commit The lock gives "dev-main 18f1a03" and Composer prints "dev-main", so every update of a site with a package on a branch stopped and asked for consent. Two different references still count as a change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Docker Swarm names services project_service, and the hostname filter refused them, so the installer could not reach a database in such a stack. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…base The name check refused "/", so a database on a mounted folder could not be chosen in the browser. It stays a SQLite thing, and dots stay refused, so the name cannot leave core/database. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
REMOTE_ADDR behind a proxy is the proxy, so the address check of the lock compared one value for everyone, and on the same host that value is loopback, which may take over a lock: a stranger could take a running install. The lock now keeps the forwarded client next to REMOTE_ADDR. Without a proxy nothing changes, and the header cannot be used to pass for another address. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ment overrides
The connection config asks env('DB_*') first, so what the form said about
those settings was dropped without a word.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s release An update keeps the site's .htaccess, so a site installed before a rule was added stays without it: on 3.5.8 updated to 3.5.9 dumps under assets could be read and PHP under assets ran. The file stays the operator's: make:site and the dashboard name what is missing, and the warning can be hidden. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
symfony/filesystem, http-foundation, http-kernel and routing go to 7.4.23. composer audit found seven advisories in them, CVE-2026-118516 among them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sories-3.5 fix(deps): update symfony packages for the advisories of 2026-11-04
…ion-cms#2553) The save redirected to the user list, which needs edit_user, so a role without it saw "You don't have enough privileges" and lost the tab even though the password had changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The form changes the viewer's own password only, yet a second manager opening it while another had it open was refused with the privilege error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A role with delete_user but not edit_user deleted the user and then got the privilege error from the list. It now lands on the dashboard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ord-privilege-error-3.5 fix(manager): stop the false privilege error after changing a password
…moval-3.5 fix(installer): make "remove the install folder" work
The lock kept only "dev-main", so a restore resolved the branch wherever it stood by then, and treating a commit on one side as equal let that pass without consent. The commit now travels with the pin (dev-main#<commit>), through the lock and the record, and a branch whose commit is not known on both sides counts as a change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The user editor stores any value and the settings merge took it as it was. The manager theme normalises it, but a manager on the front end kept it, and it reached include in the error handler, the updater and extras plugins and the store. Only a locale name now gets into the configuration, which also covers packages that read the setting, and those includes ask for one too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Composer prints the dist reference of a dev package that has no source, so an unchanged path package read as a change and needed consent on every update. Keep that reference in the pin and the record to compare, and drop it before anything reaches Composer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…isher-default-preset-3.5 Drop settings and bk_manager from the default Publisher preset
…r-composer-auth fix(updater): preserve Composer authentication during preflight
…h-publish-permission-3.5 # Conflicts: # core/tests/Unit/Security/ManagerAuthorizationGapsTest.php
… schemes The Preview button decided sandbox vs. window by a text prefix of the site URL, so same-origin links such as /page, //host/page, a different letter case or a path outside a subdirectory installation opened unsandboxed, and a javascript: weblink ran in a same-origin window. preview-target-helper.js now parses the target against the site URL, sandboxes anything on the site's or the manager's origin, opens only external http(s) links (noopener) and refuses every other scheme. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n-use-delete-3.5 # Conflicts: # core/src/Controllers/UserRoles/UserRole.php
The delete now asks delete_role and the reach guard before the in-use rule, so the behavioral harness supplies getCore() with the actor's permissions, the session role and the permissions table. Covers an in-use role (refused, permissions kept), an unused role (deleted), a missing delete_role and a role beyond the actor's reach. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dule sync A document with pub_date and unpub_date both past matched the pending-publish rule of the front-end sync and of the refresh threshold, so every sync published it and then unpublished it again, and the threshold stayed due: the writes, events and page cache drops repeated on every request. Both now use SiteContent::publishDocuments() / unPublishDocuments(), the scopes Refresh site already applies, so such a window ends unpublished without the flip and leaves the threshold alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n path rule Core judged a manager's own filemanager_path by realpath(), but the file managers name paths by fileManagerCanonicalPath(), which refuses spellings realpath() accepts (a ".." back over the root, a link that names a folder a second time). Both file managers then replaced the refusal with the site folder, wider than a narrowed site-wide root. fileManagerRoot() validates the override with the consumer's rule and is the value the classic manager, its ACL keys and the media browser's top-level rule use; it falls back only to the site-wide root, and refuses when that does not resolve. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ttings sys_files_checksum was computed from check_files_onlogin and stored before the security_settings check, and it is not a key the tab renders, so a manager without the permission still rewrote it, and a save with the tab hidden read a missing field. It is now computed only for a manager with the permission (from the posted list, else the stored one) and dropped from the request otherwise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-filemanager-path-containment-3.5 fix(manager): contain a per-user filemanager_path within the system root
…ew-sandbox-3.5 fix(manager): sandbox the editor resource preview
…esh-publish-permission-3.5 Require publish_document to run the due (un)publish on Refresh site
…-in-use-delete-3.5 Refuse deleting a role that is still assigned to users
…ty-settings-permission-3.5
…ttings MySQL and MariaDB match setting_name by collation (utf8mb4_general_ci by default), so a crafted USE_UDPERMS or Allow_Eval passed the case-sensitive security_settings check and then updated the use_udperms / allow_eval row. A posted name is now stored only when it is plain ASCII and, if a stored, factory, gated or derived setting matches it case-insensitively, exactly that spelling; isGated() ignores case, and every spelling of the derived sys_files_checksum is dropped from the request. The save_settings tests run the real processor on SQLite with NOCASE and, when EVO_TEST_MYSQL_HOST is set, on MySQL/MariaDB with utf8mb4_general_ci and utf8mb4_unicode_ci. Also: SecuritySettings was missing from the committed classmap, and the install stub seeders now match the core ones (security_settings for the administrator, no dead action_ok grants), which PublisherDefaultPresetTest checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every update replaces index.php and manager/index.php with the release's copies, and the dashboard then reported both as modified. The dashboard check now first records a listed file whose stored hash and current hash are both copies the CMS shipped (SystemFilesChecksum::RELEASED, every copy since 3.0), the current one this code's. A file anyone edited, before or after the update, or swapped for an older copy, is still reported. This works for make:site, the web updater, an overlay or git alike. The reset link of that warning changed state by GET without a CSRF token; it now carries one and a=2 ?b= is a guarded parameter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ComposerEnvironment, DocumentCascadeGuard, RoleGuard and UserGroupGuard came in with recent 3.5.x merges without a classmap entry, so a site on the committed classmap-authoritative vendor fails with class not found where they are used. CommittedClassmapTest reports them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: - core/composer.lock: 3.6 keeps its own lock; its symfony 8.1.11 already carries the fixes of the 2026-11-04 advisories that 3.5 took in b0c2f3a. - core/vendor/*: git-ignored on 3.6, stays deleted. - SiteUpdateCommand: the update runs inside ComposerEnvironment (3.5, fe8dd19), which sets COMPOSER_HOME and COMPOSER_AUTH before the overlay; 3.6's configureComposerHome() was a no-op after it and is removed with its tests, whose cases ComposerEnvironmentTest covers. - files.php, files.dynamic.php, shared-type-root-request.php: the file manager root comes from fileManagerRoot() (3.5, evolution-cms#2571). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SystemFilesChecksumTest requires this code's copy last; 3.6's index.php differs from 3.5's, manager/index.php does not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On 3.6 the processor removes the theme's compiled stylesheet through the ManagerTheme facade, which the stand-alone fixture has no application for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rity-settings-permission-3.5 add(manager): gate the Security settings tab behind a security_settings permission
|
One upgrade case still looks worth fixing before merging b64350b. In UserRole.php (around lines 247–271), the new role reach checks compare the target role's stored grants with the actor's defined permission keys. Older installations can still have the obsolete role_actionok grant from the previous default seeders. Updating the seed data does not remove that existing row, and I could not find an upgrade cleanup for it. That can leave a non-administrator role manager unable to edit, delete or assign a legacy default role even when they hold every real permission it grants: the obsolete grant remains on the target side but can never appear among the actor's defined permissions. Please either migrate/clean up those obsolete grants or consistently restrict the comparison to defined permissions, while preserving the checks for all legitimate permissions. An upgrade regression test should start with the pre-existing obsolete grant, verify the allowed role operations, and still reject operations when the actor lacks a real permission required by the target role. This is a static review finding; I have not reproduced it at runtime. SokilAI |
Saving, deleting or assigning a role compared the target role's stored grants with the actor's defined permission keys. Sites installed with the earlier default seeders still give every default role role_actionok, a key no permission defines and no update removes, so a role manager holding every real permission of such a role was refused it. An always-on permission (action_ok on those sites) stored on the target by a later save but not on the actor's role blocked the same way. RoleGuard::reachOf() returns a role's grants of defined permissions that a role may hold or not, and both reach checks (role save/delete, role assignment on user save) use it. Every real permission still counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Role reach is judged by defined, optional permissions only, so a legacy role_actionok grant no longer locks a default role away from a role manager (review of evolution-cms#2577). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, confirmed at runtime: the save was refused. The reach checks come from 3.5.x, so the fix is #2578 against 3.5.x, merged here in 95d7b68. Confirmed: the default Editor role on an upgraded site holds Fix: I restricted the comparison rather than adding a cleanup migration, so it covers any grant left without a definition, not just Upgrade regression test (
The allowed case fails on b64350b. Full suite on this branch: 2680 passed, 12 skipped (3.5.x: 2476 passed). It still merges into 3.6.x without conflicts. |
Brings everything merged into 3.5.x since the last sync into 3.6.x, together with #2572, which is still open against 3.5.x.
3.5.x PRs included: #2544 #2549 #2550 #2551 #2554 #2556 #2558 #2562 #2565 #2566 #2567 #2568 #2569 #2570 #2571 #2573 #2574 #2575 #2576, plus #2572.
Conflicts and how they were resolved
core/composer.lock: 3.6 keeps its own lock. Its symfony 8.1.11 already includes the fixes for the 2026-11-04 advisories that 3.5 took in b0c2f3a.core/vendor/*: git-ignored on 3.6, so it stays deleted.SiteUpdateCommand: the update now runs insideComposerEnvironment(3.5, fe8dd19), which setsCOMPOSER_HOMEandCOMPOSER_AUTHbefore the overlay. 3.6'sconfigureComposerHome()did nothing after that, so it is removed along with its two tests;ComposerEnvironmentTestcovers their cases (legacy site auth, implicit or explicit home, a worker without a home).files.php,files.dynamic.php,shared-type-root-request.php: the file manager root comes fromfileManagerRoot()(fix(manager): contain a per-user filemanager_path within the system root #2571).3.6-specific follow-ups
SystemFilesChecksum::RELEASEDlists 3.6'sindex.phpas the current shipped copy (manager/index.phpis the same as 3.5's), so an update from any 3.5 release to 3.6 does not report it as modified.save_settingstest fixture provides theManagerThemefacade, which the 3.6 processor uses to remove the compiled theme stylesheet.Verification
evo-ci-php:8.4: 2674 passed, 12 skipped.save_settingstests also pass on MariaDB 11.4 (utf8mb4_general_ci and utf8mb4_unicode_ci).index.phpandmanager/index.phpis inRELEASED, and the last entries match this tree.🤖 Generated with Claude Code