Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions core/src/Controllers/MoveDocument.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<?php namespace EvolutionCMS\Controllers;

use EvolutionCMS\Interfaces\ManagerTheme;
use EvolutionCMS\Models;
use EvolutionCMS\Legacy\Permissions;
use Exception;
<?php namespace EvolutionCMS\Controllers;

use EvolutionCMS\Interfaces\ManagerTheme;
use EvolutionCMS\Models;
use EvolutionCMS\Legacy\Permissions;
use EvolutionCMS\Support\MoveDocumentTargetGuard;
use Exception;

class MoveDocument extends AbstractController implements ManagerTheme\PageControllerInterface
{
Expand Down Expand Up @@ -77,13 +78,13 @@ protected function handle()
} else {
$newParentID = $newParent;
}
}
if ($newParentID > 0) {
$parentDocument = $this->getDocument($newParentID);
if ($parentDocument->deleted) {
$this->managerTheme->alertAndQuit('error_parent_deleted');
};
$children = allChildren($document->getKey());
}
if ($newParentID > 0) {
$parentDocument = $this->getDocument($newParentID);
if (MoveDocumentTargetGuard::blocksParent($parentDocument)) {
$this->managerTheme->alertAndQuit('error_parent_deleted');
};
$children = allChildren($document->getKey());
if (\in_array($parentDocument->getKey(), $children, true)) {
$this->managerTheme->alertAndQuit('You cannot move a document to a child document!', false);
}
Expand Down
13 changes: 13 additions & 0 deletions core/src/Support/MoveDocumentTargetGuard.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace EvolutionCMS\Support;

use EvolutionCMS\Models\SiteContent;

class MoveDocumentTargetGuard
{
public static function blocksParent(?SiteContent $parentDocument): bool
{
return $parentDocument === null || (int)$parentDocument->deleted === 1;
}
}
16 changes: 16 additions & 0 deletions core/tests/Unit/MoveDocumentTargetGuardTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

use EvolutionCMS\Models\SiteContent;
use EvolutionCMS\Support\MoveDocumentTargetGuard;

test('move document target guard blocks missing or deleted parents', function () {
$deletedParent = new SiteContent();
$deletedParent->deleted = 1;

$activeParent = new SiteContent();
$activeParent->deleted = 0;

expect(MoveDocumentTargetGuard::blocksParent(null))->toBeTrue()
->and(MoveDocumentTargetGuard::blocksParent($deletedParent))->toBeTrue()
->and(MoveDocumentTargetGuard::blocksParent($activeParent))->toBeFalse();
});
24 changes: 24 additions & 0 deletions manager/media/script/tests/tree-drop-guard-helper.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const helper = require('../tree-drop-guard-helper');

test('canDropIntoTarget blocks deleted tree nodes', () => {
assert.equal(helper.canDropIntoTarget({
dataset: {
deleted: '1'
}
}), false);
});

test('canDropIntoTarget allows active tree nodes', () => {
assert.equal(helper.canDropIntoTarget({
dataset: {
deleted: '0'
}
}), true);
});

test('canDropIntoTarget allows targets without dataset metadata', () => {
assert.equal(helper.canDropIntoTarget(null), true);
assert.equal(helper.canDropIntoTarget({}), true);
});
28 changes: 28 additions & 0 deletions manager/media/script/tree-drop-guard-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
(function (root, factory) {
var exported = factory();

if (typeof module === 'object' && module.exports) {
module.exports = exported;
}

root.modxTreeDropGuardHelper = exported;
}(typeof globalThis !== 'undefined' ? globalThis : this, function () {
'use strict';

function isDeletedTarget(targetAnchor) {
if (!targetAnchor || !targetAnchor.dataset) {
return false;
}

return parseInt(targetAnchor.dataset.deleted || '0', 10) === 1;
}

function canDropIntoTarget(targetAnchor) {
return !isDeletedTarget(targetAnchor);
}

return {
canDropIntoTarget: canDropIntoTarget,
isDeletedTarget: isDeletedTarget
};
}));
4 changes: 3 additions & 1 deletion manager/media/style/default/ajax.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use EvolutionCMS\Models\SiteContent;
use EvolutionCMS\Support\MoveDocumentTargetGuard;

define('IN_MANAGER_MODE', true); // we use this to make sure files are accessed through
define('MODX_API_MODE', true);
Expand Down Expand Up @@ -594,7 +595,8 @@
$parent = $eventParent;
}
}
$parentDeleted = $parent > 0 && empty(SiteContent::find($parent));
$parentDocument = $parent > 0 ? SiteContent::withTrashed()->find($parent) : null;
$parentDeleted = $parent > 0 && MoveDocumentTargetGuard::blocksParent($parentDocument);
if ($parentDeleted) {
$json['errors'] = $_lang['error_parent_deleted'];
} elseif (empty($json['errors'])) {
Expand Down
24 changes: 22 additions & 2 deletions manager/media/style/default/js/modx.js
Original file line number Diff line number Diff line change
Expand Up @@ -906,8 +906,14 @@
e.dataTransfer.dropEffect = 'all';
e.dataTransfer.setData('text', this.id.substr(4));
},
isBlockedDropTarget: function (target) {
return !!(w.modxTreeDropGuardHelper && !w.modxTreeDropGuardHelper.canDropIntoTarget(target));
},
ondragenter: function (e) {
if (d.getElementById('node' + modx.tree.itemToChange) === (this.parentNode.closest('#node' + modx.tree.itemToChange) || this.parentNode)) {
if (
d.getElementById('node' + modx.tree.itemToChange) === (this.parentNode.closest('#node' + modx.tree.itemToChange) || this.parentNode)
|| modx.tree.isBlockedDropTarget(this)
) {
this.parentNode.className = '';
e.dataTransfer.effectAllowed = 'none';
e.dataTransfer.dropEffect = 'none';
Expand All @@ -921,7 +927,12 @@
e.preventDefault();
},
ondragover: function (e) {
if (modx.tree.drag) {
if (modx.tree.isBlockedDropTarget(this)) {
this.parentNode.className = '';
e.dataTransfer.effectAllowed = 'none';
e.dataTransfer.dropEffect = 'none';
modx.tree.drag = false;
} else if (modx.tree.drag) {
var a = e.clientY;
var b = parseInt(this.getBoundingClientRect().top);
var c = a - b;
Expand Down Expand Up @@ -960,6 +971,15 @@
e.preventDefault();
},
ondrop: function (e) {
if (modx.tree.isBlockedDropTarget(this)) {
this.parentNode.removeAttribute('class');
this.parentNode.removeAttribute('draggable');
modx.alert(modx.lang.error_parent_deleted);
modx.tree.restoreTree();
e.preventDefault();
return;
}

let el = d.getElementById('node' + modx.tree.itemToChange);
let els = null;
let id = modx.tree.itemToChange;
Expand Down
4 changes: 3 additions & 1 deletion manager/media/style/liquid/ajax.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use EvolutionCMS\Models\SiteContent;
use EvolutionCMS\Support\MoveDocumentTargetGuard;

define('IN_MANAGER_MODE', true); // we use this to make sure files are accessed through
define('MODX_API_MODE', true);
Expand Down Expand Up @@ -594,7 +595,8 @@
$parent = $eventParent;
}
}
$parentDeleted = $parent > 0 && empty(SiteContent::find($parent));
$parentDocument = $parent > 0 ? SiteContent::withTrashed()->find($parent) : null;
$parentDeleted = $parent > 0 && MoveDocumentTargetGuard::blocksParent($parentDocument);
if ($parentDeleted) {
$json['errors'] = $_lang['error_parent_deleted'];
} elseif (empty($json['errors'])) {
Expand Down
24 changes: 22 additions & 2 deletions manager/media/style/liquid/js/modx.js
Original file line number Diff line number Diff line change
Expand Up @@ -923,8 +923,14 @@
e.dataTransfer.dropEffect = 'all';
e.dataTransfer.setData('text', this.id.substr(4));
},
isBlockedDropTarget: function (target) {
return !!(w.modxTreeDropGuardHelper && !w.modxTreeDropGuardHelper.canDropIntoTarget(target));
},
ondragenter: function (e) {
if (d.getElementById('node' + modx.tree.itemToChange) === (this.parentNode.closest('#node' + modx.tree.itemToChange) || this.parentNode)) {
if (
d.getElementById('node' + modx.tree.itemToChange) === (this.parentNode.closest('#node' + modx.tree.itemToChange) || this.parentNode)
|| modx.tree.isBlockedDropTarget(this)
) {
this.parentNode.className = '';
e.dataTransfer.effectAllowed = 'none';
e.dataTransfer.dropEffect = 'none';
Expand All @@ -938,7 +944,12 @@
e.preventDefault();
},
ondragover: function (e) {
if (modx.tree.drag) {
if (modx.tree.isBlockedDropTarget(this)) {
this.parentNode.className = '';
e.dataTransfer.effectAllowed = 'none';
e.dataTransfer.dropEffect = 'none';
modx.tree.drag = false;
} else if (modx.tree.drag) {
var a = e.clientY;
var b = parseInt(this.getBoundingClientRect().top);
var c = a - b;
Expand Down Expand Up @@ -977,6 +988,15 @@
e.preventDefault();
},
ondrop: function (e) {
if (modx.tree.isBlockedDropTarget(this)) {
this.parentNode.removeAttribute('class');
this.parentNode.removeAttribute('draggable');
modx.alert(modx.lang.error_parent_deleted);
modx.tree.restoreTree();
e.preventDefault();
return;
}

let el = d.getElementById('node' + modx.tree.itemToChange);
let els = null;
let id = modx.tree.itemToChange;
Expand Down
2 changes: 2 additions & 0 deletions manager/views/frame/1.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ function iconHtml($icon, $attrs = '') {
empty_recycle_bin: "{{ManagerTheme::getLexicon('empty_recycle_bin')}}",
empty_recycle_bin_empty: "{{ManagerTheme::getLexicon('empty_recycle_bin_empty')}}",
error_no_privileges: "{{ManagerTheme::getLexicon('error_no_privileges')}}",
error_parent_deleted: "{{ManagerTheme::getLexicon('error_parent_deleted')}}",
expand_tree: "{{ManagerTheme::getLexicon('expand_tree')}}",
loading_doc_tree: "{{ManagerTheme::getLexicon('loading_doc_tree')}}",
loading_menu: "{{ManagerTheme::getLexicon('loading_menu')}}",
Expand Down Expand Up @@ -218,6 +219,7 @@ function iconHtml($icon, $attrs = '') {
echo (empty($opened) ? '' : 'modx.openedArray[' . implode("] = 1;\n modx.openedArray[", $opened) . '] = 1;') . "\n";
?>
</script>
<script src="media/script/tree-drop-guard-helper.js?v={{evo()->getVersionData('version')}}"></script>
<script src="{{ManagerTheme::getThemeUrl()}}js/modx.js?v={{evo()->getVersionData('version')}}"></script>
@if ($modx->getConfig('show_picker'))
<script src="media/script/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
Expand Down