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
9 changes: 9 additions & 0 deletions config/api/routes/changes.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,13 @@
);
}
],
[
'pattern' => '(:all)/changes/unlock',
'method' => 'POST',
'action' => function (string $path) {
return Changes::unlock(
model: Find::parent($path),
);
}
]
];
7 changes: 7 additions & 0 deletions i18n/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
"error.content.lock.replace": "The version is locked and cannot be replaced",
"error.content.lock.update": "The version is locked and cannot be updated",

"error.content.unlock.invalidVersion": "Only the changes version can be unlocked",

"error.entries.max.plural": "You must not add more than {max} entries",
"error.entries.max.singular": "You must not add more than one entry",
"error.entries.min.plural": "You must add at least {min} entries",
Expand Down Expand Up @@ -140,6 +142,7 @@
"error.file.type.forbidden": "You are not allowed to upload {type} files",
"error.file.type.invalid": "Invalid file type: {type}",
"error.file.undefined": "The file cannot be found",
"error.file.unlock.permission": "You are not allowed to unlock the content of the file",

"error.form.incomplete": "Please fix all form errors…",
"error.form.notSaved": "The form could not be saved",
Expand Down Expand Up @@ -199,6 +202,7 @@
"error.page.sort.permission": "The page \"{slug}\" cannot be sorted",
"error.page.status.invalid": "Please set a valid page status",
"error.page.undefined": "The page cannot be found",
"error.page.unlock.permission": "You are not allowed to unlock the content for the page",
"error.page.update.permission": "You are not allowed to update \"{slug}\"",

"error.section.files.max.plural": "You must not add more than {max} files to the \"{section}\" section",
Expand All @@ -216,6 +220,7 @@

"error.site.changeTitle.empty": "The title must not be empty",
"error.site.changeTitle.permission": "You are not allowed to change the title of the site",
"error.site.unlock.permission": "You are not allowed to unlock the content of the site",
"error.site.update.permission": "You are not allowed to update the site",

"error.structure.validation": "There's an error on the \"{field}\" field in row {index}",
Expand Down Expand Up @@ -247,6 +252,7 @@
"error.user.password.wrong": "Wrong password",
"error.user.role.invalid": "Please enter a valid role",
"error.user.undefined": "The user cannot be found",
"error.user.unlock.permission": "You are not allowed to unlock the content of the user",
"error.user.update.permission": "You are not allowed to update the user \"{name}\"",

"error.validation.accepted": "Please confirm",
Expand Down Expand Up @@ -508,6 +514,7 @@
"lock.unsaved.users": "Unsaved accounts",
"lock.isLocked": "Unsaved changes by {email}",
"lock.unlock": "Unlock",
"lock.unlock.confirm": "You will take over the changes from <strong>{{ email }}</strong>. They will then be unable to edit the content.",
"lock.unlock.submit": "Unlock and overwrite unsaved changes by <strong>{email}</strong>",
"lock.isUnlocked": "Was unlocked by another user",

Expand Down
31 changes: 30 additions & 1 deletion panel/src/components/Forms/FormControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@
{{ $t("form.preview") }}
</k-dropdown-item>
</template>
<template v-if="isLocked && isUnlockable">
<hr />
<k-dropdown-item icon="lock" @click="unlock">
{{ $t("lock.unlock") }}
</k-dropdown-item>
</template>
</k-dropdown-content>
</div>
</template>
Expand All @@ -58,6 +64,7 @@ export const props = {
hasDiff: Boolean,
isLocked: Boolean,
isProcessing: Boolean,
isUnlockable: Boolean,
modified: [String, Date],
/**
* Preview URL for changes
Expand All @@ -76,7 +83,7 @@ export const props = {
*/
export default {
mixins: [props],
emits: ["discard", "submit"],
emits: ["discard", "submit", "unlock"],
computed: {
buttons() {
if (this.isLocked === true) {
Expand Down Expand Up @@ -138,6 +145,28 @@ export default {
}
}
});
},
unlock() {
this.$panel.dialog.open({
component: "k-text-dialog",
props: {
size: "small",
submitButton: {
icon: "lock",
theme: "negative",
text: this.$t("lock.unlock")
},
text: this.$t("lock.unlock.confirm", {
email: this.editor
})
},
on: {
submit: () => {
this.$panel.dialog.close();
this.$emit("unlock");
}
}
});
}
}
};
Expand Down
2 changes: 2 additions & 0 deletions panel/src/components/Views/Files/FileView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
:has-diff="hasDiff"
:is-locked="isLocked"
:is-processing="isSaving"
:is-unlockable="permissions.unlock"
:modified="modified"
@discard="onDiscard"
@submit="onSubmit"
@unlock="onUnlock"
/>
</template>
</k-header>
Expand Down
8 changes: 8 additions & 0 deletions panel/src/components/Views/ModelView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ export default {

this.$panel.view.refresh();
},
async onUnlock() {
await this.$panel.content.unlock({
api: this.api,
language: this.$panel.language.code
});

this.$panel.view.refresh();
},
onInput(values) {
// update the content for the current view
// this will also refresh the content prop
Expand Down
2 changes: 2 additions & 0 deletions panel/src/components/Views/Pages/PageView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
:has-diff="hasDiff"
:is-locked="isLocked"
:is-processing="isSaving"
:is-unlockable="permissions.unlock"
:modified="modified"
:preview="permissions.preview ? api + '/preview/changes' : false"
@discard="onDiscard"
@submit="onSubmit"
@unlock="onUnlock"
/>
</template>
</k-header>
Expand Down
2 changes: 2 additions & 0 deletions panel/src/components/Views/Pages/SiteView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
:has-diff="hasDiff"
:is-locked="isLocked"
:is-processing="isSaving"
:is-unlockable="permissions.unlock"
:modified="modified"
:preview="permissions.preview ? api + '/preview/changes' : false"
@discard="onDiscard"
@submit="onSubmit"
@unlock="onUnlock"
/>
</template>
</k-header>
Expand Down
2 changes: 2 additions & 0 deletions panel/src/components/Views/Users/UserView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@
:has-diff="hasDiff"
:is-locked="isLocked"
:is-processing="isSaving"
:is-unlockable="permissions.unlock"
:modified="modified"
@discard="onDiscard"
@submit="onSubmit"
@unlock="onUnlock"
/>
</template>
</k-header>
Expand Down
29 changes: 29 additions & 0 deletions panel/src/panel/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,35 @@ export default (panel) => {
*/
saveAbortController: null,

/**
* Unlocks the content for the current editor
*/
async unlock(env = {}) {
if (this.isProcessing === true) {
return;
}

// Only discard changes from the current view
if (this.isCurrent(env) === false) {
throw new Error("Cannot unlock content from another view");
}

// Check the lock state to determine if we can unlock
if (this.isLocked(env) === false) {
throw new Error("The content is not locked");
}

// Start processing the request
this.isProcessing = true;

try {
await this.request("unlock", {}, env);
this.emit("unlock", {}, env);
} finally {
this.isProcessing = false;
}
},

/**
* Updates the form values of the current view
*/
Expand Down
12 changes: 12 additions & 0 deletions src/Api/Controller/Changes.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,16 @@ public static function save(ModelWithContent $model, array $input): array
'status' => 'ok'
];
}

/**
* Unlocks the content for the current user
*/
public static function unlock(ModelWithContent $model): array
{
$model->version('changes')->unlock();

return [
'status' => 'ok'
];
}
}
1 change: 1 addition & 0 deletions src/Cms/FileBlueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public function __construct(array $props)
'read' => null,
'replace' => null,
'sort' => null,
'unlock' => null,
'update' => null,
]
);
Expand Down
1 change: 1 addition & 0 deletions src/Cms/PageBlueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public function __construct(array $props)
'preview' => null,
'read' => null,
'sort' => null,
'unlock' => null,
'update' => null,
],
// aliases (from v2)
Expand Down
5 changes: 5 additions & 0 deletions src/Cms/Permissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class Permissions
'read' => true,
'replace' => true,
'sort' => true,
'unlock' => true,
'update' => true
],
'languages' => [
Expand All @@ -59,10 +60,12 @@ class Permissions
'preview' => true,
'read' => true,
'sort' => true,
'unlock' => true,
'update' => true
],
'site' => [
'changeTitle' => true,
'unlock' => true,
'update' => true
],
'users' => [
Expand All @@ -73,6 +76,7 @@ class Permissions
'changeRole' => true,
'create' => true,
'delete' => true,
'unlock' => true,
'update' => true
],
'user' => [
Expand All @@ -82,6 +86,7 @@ class Permissions
'changePassword' => true,
'changeRole' => true,
'delete' => true,
'unlock' => true,
'update' => true
]
];
Expand Down
1 change: 1 addition & 0 deletions src/Cms/SiteBlueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public function __construct(array $props)
// defaults
[
'changeTitle' => null,
'unlock' => null,
'update' => null,
],
// aliases
Expand Down
1 change: 1 addition & 0 deletions src/Cms/UserBlueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public function __construct(array $props)
'changePassword' => null,
'changeRole' => null,
'delete' => null,
'unlock' => null,
'update' => null,
]
);
Expand Down
25 changes: 25 additions & 0 deletions src/Content/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -684,4 +684,29 @@ protected function urlWithQueryParams(string $baseUrl, string $token): string

return $uri->toString();
}

/**
* Unlocks the content for the current user
*/
public function unlock(Language|string $language = 'default'): void
{
$language = Language::ensure($language);

// check if unlocking is allowed
VersionRules::unlock($this, $language);

// get the previous state to not lose any changes
$fields = $this->read($language);

// update the version with the previous state
$this->model->storage()->update(
versionId: $this->id,
language: $language,
fields: $this->prepareFieldsBeforeWrite($fields, $language)
);

// remove the version from the cache to read
// a fresh version next time
VersionCache::remove($this, $language);
}
}
20 changes: 20 additions & 0 deletions src/Content/VersionRules.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Kirby\Cms\Language;
use Kirby\Exception\LogicException;
use Kirby\Exception\NotFoundException;
use Kirby\Exception\PermissionException;

/**
* The VersionRules class handles the validation for all
Expand Down Expand Up @@ -144,6 +145,25 @@ public static function touch(
static::ensure($version, $language);
}

public static function unlock(
Version $version,
Language $language
): void {
static::ensure($version, $language);

if ($version->id()->is('changes') === false) {
throw new LogicException(
key: 'content.unlock.invalidVersion'
);
}

if ($version->model()->permissions()->can('unlock') !== true) {
throw new PermissionException(
key: $version->model()::CLASS_ALIAS . '.unlock.permission',
);
}
}

public static function update(
Version $version,
array $fields,
Expand Down
Loading