+ * @link https://getkirby.com
+ * @copyright Bastian Allgeier
+ * @license https://getkirby.com/license
+ */
class ChangesDialog
{
- public function changes(array $ids = []): array
- {
- $kirby = App::instance();
- $multilang = $kirby->multilang();
- $changes = [];
-
- foreach ($ids as $id) {
- try {
- // parse the given ID to extract
- // the path and an optional query
- $uri = new Uri($id);
- $path = $uri->path()->toString();
- $query = $uri->query();
- $model = Find::parent($path);
- $item = $model->panel()->dropdownOption();
-
- // add the language to each option, if it is included in the query
- // of the given ID and the language actually exists
- if (
- $multilang &&
- $query->language &&
- $language = $kirby->language($query->language)
- ) {
- $item['text'] .= ' (' . $language->code() . ')';
- $item['link'] .= '?language=' . $language->code();
- }
-
- $item['text'] = Escape::html($item['text']);
-
- $changes[] = $item;
- } catch (Throwable) {
- continue;
- }
- }
+ public function __construct(
+ protected Changes $changes = new Changes()
+ ) {
+ }
- return $changes;
+ /**
+ * Returns the item props for all changed files
+ */
+ public function files(): array
+ {
+ return $this->items($this->changes->files());
}
- public function load(): array
+ /**
+ * Helper method to return item props for the given models
+ */
+ public function items(Collection $models): array
{
- return $this->state();
+ return $models->values(
+ fn ($model) => $model->panel()->dropdownOption()
+ );
}
- public function state(bool $loading = true, array $changes = [])
+ /**
+ * Returns the backend full definition for dialog
+ */
+ public function load(): array
{
+ if ($this->changes->cacheExists() === false) {
+ $this->changes->generateCache();
+ }
+
return [
'component' => 'k-changes-dialog',
'props' => [
- 'changes' => $changes,
- 'loading' => $loading
+ 'files' => $this->files(),
+ 'pages' => $this->pages(),
+ 'users' => $this->users(),
]
];
}
- public function submit(array $ids): array
+ /**
+ * Returns the item props for all changed pages
+ */
+ public function pages(): array
+ {
+ return $this->items($this->changes->pages());
+ }
+
+ /**
+ * Returns the item props for all changed users
+ */
+ public function users(): array
{
- return $this->state(false, $this->changes($ids));
+ return $this->items($this->changes->users());
}
}
diff --git a/src/Panel/Controller/Changes.php b/src/Panel/Controller/Changes.php
deleted file mode 100644
index 93fbc767bc..0000000000
--- a/src/Panel/Controller/Changes.php
+++ /dev/null
@@ -1,107 +0,0 @@
-
- * @link https://getkirby.com
- * @copyright Bastian Allgeier
- * @license https://getkirby.com/license
- */
-class Changes
-{
- /**
- * Discards unpublished changes by deleting the version
- */
- public static function discard(ModelWithContent $model): array
- {
- $model->version(VersionId::changes())->delete();
-
- // remove the model from the user's list of unsaved changes
- App::instance()->site()->changes()->untrack($model);
-
- return [
- 'status' => 'ok'
- ];
- }
-
- /**
- * Saves the lastest state of changes first and then publishs them
- */
- public static function publish(ModelWithContent $model, array $input): array
- {
- // save the given changes first
- static::save(
- model: $model,
- input: $input
- );
-
- // get the changes version
- $changes = $model->version(VersionId::changes());
-
- // publish the changes
- $changes->publish(
- language: 'current'
- );
-
- // remove the model from the user's list of unsaved changes
- App::instance()->site()->changes()->untrack($model);
-
- return [
- 'status' => 'ok'
- ];
- }
-
- /**
- * Saves form input in a new or existing `changes` version
- */
- public static function save(ModelWithContent $model, array $input): array
- {
- // we need to run the input through the form
- // class to get a set of storable field values
- // that we can send to the content storage handler
- $form = Form::for($model, [
- 'ignoreDisabled' => true,
- 'input' => $input,
- ]);
-
- // combine the new field changes with the
- // last published state
- $model->version(VersionId::changes())->save(
- fields: [
- ...$model->version(VersionId::latest())->read(),
- ...$form->strings(),
- ],
- language: 'current'
- );
-
- // add the model to the user's list of unsaved changes
- App::instance()->site()->changes()->track($model);
-
- return [
- 'status' => 'ok'
- ];
- }
-
- /**
- * Removes the user lock from a `changes` version
- */
- public static function unlock(ModelWithContent $model): array
- {
- throw new Exception(message: 'Not yet implemented');
-
- return [
- 'status' => 'ok'
- ];
- }
-}
diff --git a/src/Panel/File.php b/src/Panel/File.php
index b97441462b..af714ae442 100644
--- a/src/Panel/File.php
+++ b/src/Panel/File.php
@@ -401,29 +401,38 @@ public function pickerData(array $params = []): array
*/
public function props(): array
{
- $file = $this->model;
- $dimensions = $file->dimensions();
+ $props = parent::props();
+ $file = $this->model;
+
+ // Additional model information
+ // @deprecated Use the top-level props instead
+ $model = [
+ 'content' => $props['content'],
+ 'dimensions' => $file->dimensions()->toArray(),
+ 'extension' => $file->extension(),
+ 'filename' => $file->filename(),
+ 'link' => $props['link'],
+ 'mime' => $file->mime(),
+ 'niceSize' => $file->niceSize(),
+ 'id' => $props['id'],
+ 'parent' => $file->parent()->panel()->path(),
+ 'template' => $file->template(),
+ 'type' => $file->type(),
+ 'url' => $file->url(),
+ 'uuid' => $props['uuid'],
+ ];
return [
- ...parent::props(),
+ ...$props,
...$this->prevNext(),
'blueprint' => $this->model->template() ?? 'default',
- 'model' => [
- 'content' => $this->content(),
- 'dimensions' => $dimensions->toArray(),
- 'extension' => $file->extension(),
- 'filename' => $file->filename(),
- 'link' => $this->url(true),
- 'mime' => $file->mime(),
- 'niceSize' => $file->niceSize(),
- 'id' => $id = $file->id(),
- 'parent' => $file->parent()->panel()->path(),
- 'template' => $file->template(),
- 'type' => $file->type(),
- 'url' => $file->url(),
- 'uuid' => fn () => $file->uuid()?->toString(),
- ],
- 'preview' => FilePreview::factory($this->model)->render()
+ 'extension' => $model['extension'],
+ 'filename' => $model['filename'],
+ 'mime' => $model['mime'],
+ 'model' => $model,
+ 'preview' => FilePreview::factory($this->model)->render(),
+ 'type' => $model['type'],
+ 'url' => $model['url'],
];
}
diff --git a/src/Panel/Lab/Docs.php b/src/Panel/Lab/Docs.php
index 3e26a0f90a..62834dd1fb 100644
--- a/src/Panel/Lab/Docs.php
+++ b/src/Panel/Lab/Docs.php
@@ -52,7 +52,7 @@ function ($file) {
return [
'image' => [
'icon' => 'book',
- 'back' => 'white',
+ 'back' => 'light-dark(white, var(--color-gray-800))',
],
'text' => $component,
'link' => '/lab/docs/' . $component,
diff --git a/src/Panel/Lab/Example.php b/src/Panel/Lab/Example.php
index d4d74e6195..07ce6f2dde 100644
--- a/src/Panel/Lab/Example.php
+++ b/src/Panel/Lab/Example.php
@@ -173,7 +173,7 @@ public function toArray(): array
return [
'image' => [
'icon' => $this->parent->icon(),
- 'back' => 'white',
+ 'back' => 'light-dark(white, var(--color-gray-800))',
],
'text' => $this->title(),
'link' => $this->url()
diff --git a/src/Panel/Model.php b/src/Panel/Model.php
index 59ba5b7662..6206d4cf76 100644
--- a/src/Panel/Model.php
+++ b/src/Panel/Model.php
@@ -40,8 +40,8 @@ public function content(): array
$version = $this->model->version('changes');
$changes = [];
- if ($version->exists() === true) {
- $changes = $version->content()->toArray();
+ if ($version->exists('current') === true) {
+ $changes = $version->content('current')->toArray();
}
// create a form which will collect the latest values for the model,
@@ -298,17 +298,6 @@ public function isDisabledDropdownOption(
$option === 'false';
}
- /**
- * Returns lock info for the Panel
- *
- * @return array|false array with lock info,
- * false if locking is not supported
- */
- public function lock(): array|false
- {
- return $this->model->lock()?->toArray() ?? false;
- }
-
/**
* Returns the corresponding model object
* @since 5.0.0
@@ -329,7 +318,7 @@ public function options(array $unlock = []): array
{
$options = $this->model->permissions()->toArray();
- if ($this->model->isLocked()) {
+ if ($this->model->lock()->isLocked() === true) {
foreach ($options as $key => $value) {
if (in_array($key, $unlock, true)) {
continue;
@@ -342,6 +331,14 @@ public function options(array $unlock = []): array
return $options;
}
+ /**
+ * Get the original content values for the model
+ */
+ public function originals(): array
+ {
+ return Form::for(model: $this->model)->values();
+ }
+
/**
* Returns the full path without leading slash
*/
@@ -375,15 +372,22 @@ public function pickerData(array $params = []): array
public function props(): array
{
$blueprint = $this->model->blueprint();
+ $link = $this->url(true);
$request = $this->model->kirby()->request();
$tabs = $blueprint->tabs();
$tab = $blueprint->tab($request->get('tab')) ?? $tabs[0] ?? null;
$props = [
+ 'api' => $link,
'buttons' => fn () => $this->buttons(),
- 'lock' => $this->lock(),
+ 'content' => (object)$this->content(),
+ 'id' => $this->model->id(),
+ 'link' => $link,
+ 'lock' => $this->model->lock()->toArray(),
+ 'originals' => (object)$this->originals(),
'permissions' => $this->model->permissions()->toArray(),
'tabs' => $tabs,
+ 'uuid' => fn () => $this->model->uuid()?->toString()
];
// only send the tab if it exists
diff --git a/src/Panel/Page.php b/src/Panel/Page.php
index 132058dff9..4b63eee132 100644
--- a/src/Panel/Page.php
+++ b/src/Panel/Page.php
@@ -92,24 +92,30 @@ public function dragText(string|null $type = null): string
*/
public function dropdown(array $options = []): array
{
- $page = $this->model;
- $request = $page->kirby()->request();
- $defaults = $request->get(['view', 'sort', 'delete']);
- $options = [...$defaults, $options];
-
+ $page = $this->model;
+ $request = $page->kirby()->request();
+ $defaults = $request->get(['view', 'sort', 'delete']);
+ $options = [...$defaults, ...$options];
$permissions = $this->options(['preview']);
$view = $options['view'] ?? 'view';
$url = $this->url(true);
$result = [];
if ($view === 'list') {
- $result['preview'] = [
+ $result['open'] = [
'link' => $page->previewUrl(),
'target' => '_blank',
'icon' => 'open',
'text' => I18n::translate('open'),
'disabled' => $this->isDisabledDropdownOption('preview', $options, $permissions)
];
+
+ $result['preview'] = [
+ 'icon' => 'window',
+ 'link' => $page->panel()->url(true) . '/preview/compare',
+ 'text' => I18n::translate('preview'),
+ ];
+
$result[] = '-';
}
@@ -346,22 +352,27 @@ public function prevNext(): array
*/
public function props(): array
{
- $page = $this->model;
+ $props = parent::props();
+
+ // Additional model information
+ // @deprecated Use the top-level props instead
+ $model = [
+ 'content' => $props['content'],
+ 'id' => $props['id'],
+ 'link' => $props['link'],
+ 'parent' => $this->model->parentModel()->panel()->url(true),
+ 'previewUrl' => $this->model->previewUrl(),
+ 'status' => $this->model->status(),
+ 'title' => $this->model->title()->toString(),
+ 'uuid' => $props['uuid'],
+ ];
return [
- ...parent::props(),
+ ...$props,
...$this->prevNext(),
- 'blueprint' => $page->intendedTemplate()->name(),
- 'model' => [
- 'content' => $this->content(),
- 'id' => $page->id(),
- 'link' => $this->url(true),
- 'parent' => $page->parentModel()->panel()->url(true),
- 'previewUrl' => $page->previewUrl(),
- 'status' => $page->status(),
- 'title' => $page->title()->toString(),
- 'uuid' => fn () => $page->uuid()?->toString(),
- ]
+ 'blueprint' => $this->model->intendedTemplate()->name(),
+ 'model' => $model,
+ 'title' => $model['title'],
];
}
@@ -376,8 +387,8 @@ public function view(): array
return [
'breadcrumb' => $this->model->panel()->breadcrumb(),
'component' => 'k-page-view',
- 'props' => $this->props(),
- 'title' => $this->model->title()->toString(),
+ 'props' => $props = $this->props(),
+ 'title' => $props['title'],
];
}
}
diff --git a/src/Panel/Site.php b/src/Panel/Site.php
index 9f117da2d3..95aff7b854 100644
--- a/src/Panel/Site.php
+++ b/src/Panel/Site.php
@@ -80,16 +80,24 @@ public function path(): string
*/
public function props(): array
{
+ $props = parent::props();
+
+ // Additional model information
+ // @deprecated Use the top-level props instead
+ $model = [
+ 'content' => $props['content'],
+ 'link' => $props['link'],
+ 'previewUrl' => $this->model->previewUrl(),
+ 'title' => $this->model->title()->toString(),
+ 'uuid' => $props['uuid'],
+ ];
+
return [
- ...parent::props(),
- 'blueprint' => 'site',
- 'model' => [
- 'content' => $this->content(),
- 'link' => $this->url(true),
- 'previewUrl' => $this->model->previewUrl(),
- 'title' => $this->model->title()->toString(),
- 'uuid' => fn () => $this->model->uuid()?->toString(),
- ]
+ ...$props,
+ 'blueprint' => 'site',
+ 'id' => '/',
+ 'model' => $model,
+ 'title' => $model['title'],
];
}
diff --git a/src/Panel/Ui/Buttons/LanguagesDropdown.php b/src/Panel/Ui/Buttons/LanguagesDropdown.php
index f4dedd93a1..26d9775b35 100644
--- a/src/Panel/Ui/Buttons/LanguagesDropdown.php
+++ b/src/Panel/Ui/Buttons/LanguagesDropdown.php
@@ -4,7 +4,9 @@
use Kirby\Cms\App;
use Kirby\Cms\Language;
+use Kirby\Cms\Languages;
use Kirby\Cms\ModelWithContent;
+use Kirby\Content\VersionId;
use Kirby\Toolkit\Str;
/**
@@ -39,13 +41,36 @@ class: 'k-languages-dropdown',
);
}
+ /**
+ * Returns if any translation other than the current one has unsaved changes
+ * (the current language has to be handled in `k-languages-dropdown` as its
+ * state can change dynamically without another backend request)
+ */
+ public function hasChanges(): bool
+ {
+ foreach (Languages::ensure() as $language) {
+ if ($this->kirby->language()?->code() !== $language->code()) {
+ if ($this->model->version(VersionId::changes())->exists($language) === true) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
public function option(Language $language): array
{
+ $changes = $this->model->version('changes');
+
return [
'text' => $language->name(),
'code' => $language->code(),
+ 'link' => $this->model->panel()->url(true) . '?language=' . $language->code(),
'current' => $language->code() === $this->kirby->language()?->code(),
- 'link' => $this->model->panel()->url(true) . '?language=' . $language->code()
+ 'default' => $language->isDefault(),
+ 'changes' => $changes->exists($language),
+ 'lock' => $changes->isLocked('*')
];
}
@@ -76,6 +101,14 @@ public function options(): array
return $options;
}
+ public function props(): array
+ {
+ return [
+ ...parent::props(),
+ 'hasChanges' => $this->hasChanges()
+ ];
+ }
+
public function render(): array|null
{
if ($this->kirby->multilang() === false) {
diff --git a/src/Panel/Ui/Buttons/PageStatusButton.php b/src/Panel/Ui/Buttons/PageStatusButton.php
index 410fb38314..44a2f7a3b2 100644
--- a/src/Panel/Ui/Buttons/PageStatusButton.php
+++ b/src/Panel/Ui/Buttons/PageStatusButton.php
@@ -32,6 +32,7 @@ public function __construct(
parent::__construct(
class: 'k-status-view-button k-page-status-button',
+ component: 'k-status-view-button',
dialog: $page->panel()->url(true) . '/changeStatus',
disabled: $disabled,
icon: 'status-' . $status,
diff --git a/src/Panel/Ui/Buttons/PreviewDropdownButton.php b/src/Panel/Ui/Buttons/PreviewDropdownButton.php
new file mode 100644
index 0000000000..0d6ed526b9
--- /dev/null
+++ b/src/Panel/Ui/Buttons/PreviewDropdownButton.php
@@ -0,0 +1,58 @@
+
+ * @link https://getkirby.com
+ * @copyright Bastian Allgeier
+ * @license https://getkirby.com/license
+ * @since 5.0.0
+ * @internal
+ */
+class PreviewDropdownButton extends ViewButton
+{
+ public function __construct(
+ public string $open,
+ public string|null $preview,
+ public string|null $copy
+ ) {
+ parent::__construct(
+ class: 'k-preview-dropdown-view-button',
+ icon: 'open',
+ options: $this->options(),
+ title: I18n::translate('open')
+ );
+ }
+
+ public function options(): array
+ {
+ return [
+ [
+ 'text' => I18n::translate('open'),
+ 'icon' => 'open',
+ 'link' => $this->open,
+ 'target' => '_blank'
+ ],
+ [
+ 'text' => I18n::translate('preview'),
+ 'icon' => 'window',
+ 'link' => $this->preview,
+ ],
+ '-',
+ [
+ 'text' => I18n::translate('copy.url'),
+ 'icon' => 'copy',
+ 'click' => [
+ 'global' => 'clipboard.write',
+ 'payload' => $this->copy
+ ]
+ ]
+ ];
+ }
+}
diff --git a/src/Panel/User.php b/src/Panel/User.php
index 62e1ab34b6..b86c6ba3ad 100644
--- a/src/Panel/User.php
+++ b/src/Panel/User.php
@@ -237,31 +237,41 @@ public function prevNext(): array
*/
public function props(): array
{
+ $props = parent::props();
$user = $this->model;
- $account = $user->isLoggedIn();
$permissions = $this->options();
+ // Additional model information
+ // @deprecated Use the top-level props instead
+ $model = [
+ 'account' => $user->isLoggedIn(),
+ 'avatar' => $user->avatar()?->url(),
+ 'content' => $props['content'],
+ 'email' => $user->email(),
+ 'id' => $props['id'],
+ 'language' => $this->translation()->name(),
+ 'link' => $props['link'],
+ 'name' => $user->name()->toString(),
+ 'role' => $user->role()->title(),
+ 'username' => $user->username(),
+ 'uuid' => $props['uuid'],
+ ];
+
return [
...parent::props(),
...$this->prevNext(),
+ 'avatar' => $model['avatar'],
'blueprint' => $this->model->role()->name(),
'canChangeEmail' => $permissions['changeEmail'],
'canChangeLanguage' => $permissions['changeLanguage'],
'canChangeName' => $permissions['changeName'],
'canChangeRole' => $this->model->roles()->count() > 1,
- 'model' => [
- 'account' => $account,
- 'avatar' => $user->avatar()?->url(),
- 'content' => $this->content(),
- 'email' => $user->email(),
- 'id' => $user->id(),
- 'language' => $this->translation()->name(),
- 'link' => $this->url(true),
- 'name' => $user->name()->toString(),
- 'role' => $user->role()->title(),
- 'username' => $user->username(),
- 'uuid' => fn () => $user->uuid()?->toString()
- ]
+ 'email' => $model['email'],
+ 'language' => $model['language'],
+ 'model' => $model,
+ 'name' => $model['name'],
+ 'role' => $model['role'],
+ 'username' => $model['username'],
];
}
diff --git a/src/Plugin/Plugin.php b/src/Plugin/Plugin.php
index 6eb2261548..de9b0a2240 100644
--- a/src/Plugin/Plugin.php
+++ b/src/Plugin/Plugin.php
@@ -29,7 +29,7 @@
class Plugin
{
protected Assets $assets;
- protected License $license;
+ protected License|Closure|array|string $license;
protected UpdateStatus|null $updateStatus = null;
/**
@@ -69,20 +69,9 @@ public function __construct(
}
// read composer.json and use as info fallback
- try {
- $info = Data::read($this->manifest());
- } catch (Exception) {
- // there is no manifest file or it is invalid
- $info = [];
- }
-
- $this->info = [...$info, ...$this->info];
-
- // set the license
- $this->license = License::from(
- plugin: $this,
- license: $license ?? $this->info['license'] ?? '-'
- );
+ $info = Data::read($this->manifest(), fail: false);
+ $this->info = [...$info, ...$this->info];
+ $this->license = $license ?? $this->info['license'] ?? '-';
}
/**
@@ -185,7 +174,11 @@ public function link(): string|null
*/
public function license(): License
{
- return $this->license;
+ // resolve license info from Closure, array or string
+ return License::from(
+ plugin: $this,
+ license: $this->license
+ );
}
/**
diff --git a/src/Toolkit/Component.php b/src/Toolkit/Component.php
index ba274f58df..54f9bb6ab0 100644
--- a/src/Toolkit/Component.php
+++ b/src/Toolkit/Component.php
@@ -145,34 +145,51 @@ public static function defaults(): array
}
/**
- * Register all defined props and apply the
- * passed values.
+ * Register a single property
*/
- protected function applyProps(array $props): void
+ protected function applyProp(string $name, mixed $value): void
{
- foreach ($props as $name => $function) {
- if ($function instanceof Closure) {
- if (isset($this->attrs[$name]) === true) {
- try {
- $this->$name = $this->props[$name] = $function->call(
- $this,
- $this->attrs[$name]
- );
- continue;
- } catch (TypeError) {
- throw new TypeError('Invalid value for "' . $name . '"');
- }
- }
+ // unset prop
+ if ($value === null) {
+ unset($this->props[$name], $this->$name);
+ return;
+ }
+
+ // apply a prop via a closure
+ if ($value instanceof Closure) {
+ if (isset($this->attrs[$name]) === true) {
try {
- $this->$name = $this->props[$name] = $function->call($this);
- continue;
- } catch (ArgumentCountError) {
- throw new ArgumentCountError('Please provide a value for "' . $name . '"');
+ $this->$name = $this->props[$name] = $value->call(
+ $this,
+ $this->attrs[$name]
+ );
+ return;
+ } catch (TypeError) {
+ throw new TypeError('Invalid value for "' . $name . '"');
}
}
- $this->$name = $this->props[$name] = $function;
+ try {
+ $this->$name = $this->props[$name] = $value->call($this);
+ return;
+ } catch (ArgumentCountError) {
+ throw new ArgumentCountError('Please provide a value for "' . $name . '"');
+ }
+ }
+
+ // simple prop assignment by value
+ $this->$name = $this->props[$name] = $value;
+ }
+
+ /**
+ * Register all defined props and apply the
+ * passed values.
+ */
+ protected function applyProps(array $props): void
+ {
+ foreach ($props as $name => $value) {
+ $this->applyProp($name, $value);
}
}
diff --git a/src/Toolkit/Xml.php b/src/Toolkit/Xml.php
index fddb0a3079..15dc9eac25 100644
--- a/src/Toolkit/Xml.php
+++ b/src/Toolkit/Xml.php
@@ -2,7 +2,6 @@
namespace Kirby\Toolkit;
-use Kirby\Cms\Helpers;
use SimpleXMLElement;
/**
@@ -97,15 +96,6 @@ public static function attr(
return null;
}
- // TODO: In 5.0, remove this block to render space as space
- // @codeCoverageIgnoreStart
- if ($value === ' ') {
- Helpers::deprecated('Passing a single space as value to `Xml::attr()` has been deprecated. In a future version, passing a single space won\'t render an empty value anymore but a single space. To render an empty value, please pass an empty string.', 'xml-attr-single-space');
-
- return $name . '=""';
- }
- // @codeCoverageIgnoreEnd
-
if ($value === true) {
return $name . '="' . $name . '"';
}
diff --git a/tests/Panel/Controller/ChangesTest.php b/tests/Api/Controller/ChangesTest.php
similarity index 71%
rename from tests/Panel/Controller/ChangesTest.php
rename to tests/Api/Controller/ChangesTest.php
index 2b3fb823b2..631dcaebc2 100644
--- a/tests/Panel/Controller/ChangesTest.php
+++ b/tests/Api/Controller/ChangesTest.php
@@ -1,6 +1,6 @@
app->impersonate('kirby');
+
Data::write($this->page->root() . '/article.txt', []);
Data::write($file = $this->page->root() . '/_changes/article.txt', []);
@@ -83,4 +85,28 @@ public function testSave()
$this->assertSame(['title' => 'Test'], $changes);
}
+
+ public function testSaveWithNoDiff()
+ {
+ Data::write($this->page->root() . '/article.txt', [
+ 'title' => 'Test'
+ ]);
+ Data::write($this->page->root() . '/_changes/article.txt', [
+ 'title' => 'Test'
+ ]);
+
+ $response = Changes::save($this->page, [
+ 'title' => 'Foo'
+ ]);
+
+ $this->assertSame(['status' => 'ok'], $response);
+ $this->assertFileExists($this->page->root() . '/_changes/article.txt');
+
+ $response = Changes::save($this->page, [
+ 'title' => 'Test'
+ ]);
+
+ $this->assertSame(['status' => 'ok'], $response);
+ $this->assertFileDoesNotExist($this->page->root() . '/_changes/article.txt');
+ }
}
diff --git a/tests/Cms/Api/ApiTest.php b/tests/Cms/Api/ApiTest.php
index 24fb274895..d1bc3e83fa 100644
--- a/tests/Cms/Api/ApiTest.php
+++ b/tests/Cms/Api/ApiTest.php
@@ -591,7 +591,7 @@ public function testFieldApiInvalidField()
]);
$this->expectException(NotFoundException::class);
- $this->expectExceptionMessage('The field "nonexists" could not be found');
+ $this->expectExceptionMessage('The field could not be found');
$page = $app->page('test');
$app->api()->fieldApi($page, 'nonexists');
@@ -608,7 +608,7 @@ public function testFieldApiEmptyField()
]);
$this->expectException(NotFoundException::class);
- $this->expectExceptionMessage('No field could be loaded');
+ $this->expectExceptionMessage('The field could not be found');
$page = $app->page('test');
$app->api()->fieldApi($page, '');
diff --git a/tests/Cms/Api/routes/LockRoutesTest.php b/tests/Cms/Api/routes/LockRoutesTest.php
deleted file mode 100644
index b0b2102f73..0000000000
--- a/tests/Cms/Api/routes/LockRoutesTest.php
+++ /dev/null
@@ -1,42 +0,0 @@
-app = new App([
- 'options' => [
- 'api.allowImpersonation' => true
- ],
- 'roots' => [
- 'index' => '/dev/null'
- ]
- ]);
- }
-
- public function testGet()
- {
- $app = $this->app->clone([
- 'site' => [
- 'children' => [
- [
- 'slug' => 'a',
- ]
- ]
- ]
- ]);
-
- $app->impersonate('kirby');
-
- $response = $app->api()->call('pages/a/lock');
- $expected = [
- 'lock' => false
- ];
-
- $this->assertSame($expected, $response);
- }
-}
diff --git a/tests/Cms/App/AppResolveTest.php b/tests/Cms/App/AppResolveTest.php
index 309a788b7d..1da298a8d6 100644
--- a/tests/Cms/App/AppResolveTest.php
+++ b/tests/Cms/App/AppResolveTest.php
@@ -75,6 +75,43 @@ public function testResolveSubPage()
$this->assertSame('test/subpage', $result->id());
}
+ public function testResolveDraft()
+ {
+ $app = new App([
+ 'roots' => [
+ 'index' => '/dev/null'
+ ],
+ 'site' => [
+ 'children' => [
+ [
+ 'slug' => 'test',
+ 'drafts' => [
+ [
+ 'slug' => 'a-draft',
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]);
+
+ $result = $app->resolve('test/a-draft');
+ $this->assertNull($result);
+
+ $app = $app->clone([
+ 'request' => [
+ 'query' => [
+ '_token' => $app->page('test/a-draft')->version()->previewToken()
+ ]
+ ]
+ ]);
+
+ $result = $app->resolve('test/a-draft');
+
+ $this->assertIsPage($result);
+ $this->assertSame('test/a-draft', $result->id());
+ }
+
public function testResolvePageRepresentation()
{
F::write($template = static::TMP . '/test.php', 'html');
diff --git a/tests/Cms/App/AppTest.php b/tests/Cms/App/AppTest.php
index 52f3ef18a4..06fb366ebb 100644
--- a/tests/Cms/App/AppTest.php
+++ b/tests/Cms/App/AppTest.php
@@ -265,14 +265,27 @@ public function testCollectionWithOptions()
*/
public function testContentToken()
{
+ $model = new class () {
+ public function id(): string
+ {
+ return 'some-id';
+ }
+
+ public function type(): string
+ {
+ return 'sea';
+ }
+ };
+
// without configured salt
$app = new App([
'roots' => [
'index' => '/dev/null'
]
]);
- $this->assertSame(hash_hmac('sha1', 'test', '/dev/null/content'), $app->contentToken('model', 'test'));
+ $this->assertSame(hash_hmac('sha1', 'test', '/dev/null/content/some-id'), $app->contentToken($model, 'test'));
$this->assertSame(hash_hmac('sha1', 'test', '/dev/null/content'), $app->contentToken($app, 'test'));
+ $this->assertSame(hash_hmac('sha1', 'test', '/dev/null/content'), $app->contentToken(null, 'test'));
// with custom static salt
$app = new App([
@@ -280,15 +293,18 @@ public function testContentToken()
'content.salt' => 'salt and pepper and chili'
]
]);
- $this->assertSame(hash_hmac('sha1', 'test', 'salt and pepper and chili'), $app->contentToken('model', 'test'));
+ $this->assertSame(hash_hmac('sha1', 'test', 'salt and pepper and chili'), $app->contentToken($model, 'test'));
+ $this->assertSame(hash_hmac('sha1', 'test', 'salt and pepper and chili'), $app->contentToken($app, 'test'));
+ $this->assertSame(hash_hmac('sha1', 'test', 'salt and pepper and chili'), $app->contentToken(null, 'test'));
// with callback
$app = new App([
'options' => [
- 'content.salt' => fn ($model) => 'salt ' . $model
+ 'content.salt' => fn (object|null $model) => $model?->type() . ' salt'
]
]);
- $this->assertSame(hash_hmac('sha1', 'test', 'salt lake city'), $app->contentToken('lake city', 'test'));
+ $this->assertSame(hash_hmac('sha1', 'test', 'sea salt'), $app->contentToken($model, 'test'));
+ $this->assertSame(hash_hmac('sha1', 'test', ' salt'), $app->contentToken(null, 'test'));
}
/**
diff --git a/tests/Cms/Content/ContentLockTest.php b/tests/Cms/Content/ContentLockTest.php
deleted file mode 100644
index dc8f99f53e..0000000000
--- a/tests/Cms/Content/ContentLockTest.php
+++ /dev/null
@@ -1,349 +0,0 @@
- [
- 'index' => static::TMP
- ],
- 'site' => [
- 'children' => [
- ['slug' => 'test'],
- ['slug' => 'foo']
- ]
- ],
- 'users' => [
- ['email' => 'test@getkirby.com'],
- ['email' => 'homer@simpson.com'],
- ['email' => 'peter@lustig.de']
- ]
- ]);
- }
-
- public function setUp(): void
- {
- $this->app = $this->app();
- Dir::make(static::TMP . '/content/test');
- }
-
- public function tearDown(): void
- {
- Dir::remove(static::TMP);
- }
-
- public function testCreate()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('test@getkirby.com');
- $this->assertTrue($page->lock()->create());
- $this->assertTrue($page->lock()->create());
-
- $this->assertNotEmpty($app->locks()->get($page));
- }
-
- public function testCreateWithExistingLock()
- {
- $this->expectException(DuplicateException::class);
- $this->expectExceptionMessage('/test is already locked');
-
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('test@getkirby.com');
- $this->assertTrue($page->lock()->create());
-
- $app->impersonate('homer@simpson.com');
- $page->lock()->create();
- }
-
- public function testCreateUnauthenticated()
- {
- $this->expectException(AuthException::class);
- $this->expectExceptionMessage('No user authenticated');
-
- $app = $this->app;
- $page = $app->page('test');
- $page->lock()->create();
- }
-
- public function testGetWithNoLock()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $this->assertFalse($page->lock()->get());
- }
-
- public function testGetWithSameUser()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('test@getkirby.com');
- $page->lock()->create();
-
- $this->assertFalse($page->lock()->get());
- }
-
- public function testGet()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('test@getkirby.com');
- $page->lock()->create();
-
- $app->impersonate('homer@simpson.com');
- $data = $page->lock()->get();
-
- $this->assertNotEmpty($data);
- $this->assertFalse($data['unlockable']);
- $this->assertSame('test@getkirby.com', $data['email']);
- $this->assertArrayHasKey('time', $data);
- }
-
- public function testGetUserMissing()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('test@getkirby.com');
- $page->lock()->create();
- $this->assertFileExists(static::TMP . '/content/test/.lock');
-
- $app->impersonate('homer@simpson.com');
- $data = $page->lock()->get();
- $this->assertFileExists(static::TMP . '/content/test/.lock');
- $this->assertNotEmpty($data);
- $this->assertFalse($data['unlockable']);
- $this->assertSame('test@getkirby.com', $data['email']);
- $this->assertArrayHasKey('time', $data);
-
- $app->users()->remove($app->user('test@getkirby.com'));
- $data = $page->lock()->get();
- $this->assertFileDoesNotExist(static::TMP . '/content/test/.lock');
- $this->assertFalse($data);
- }
-
- public function testIsLocked()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('test@getkirby.com');
- $page->lock()->create();
- $this->assertFalse($page->lock()->isLocked());
-
- $app->impersonate('homer@simpson.com');
- $this->assertTrue($page->lock()->isLocked());
- }
-
- public function testRemoveWithNoLock()
- {
- $app = $this->app;
- $page = $app->page('test');
- $app->impersonate('test@getkirby.com');
-
- $this->assertTrue($page->lock()->remove());
- }
-
- public function testRemoveFormOtherUser()
- {
- $this->expectException(LogicException::class);
- $this->expectExceptionMessage('The content lock can only be removed by the user who created it. Use unlock instead.');
-
- $app = $this->app;
- $page = $app->page('test');
- $app->impersonate('test@getkirby.com');
- $page->lock()->create();
-
- $app->impersonate('homer@simpson.com');
- $page->lock()->remove();
- }
-
- public function testRemove()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('test@getkirby.com');
-
- $this->assertTrue($page->lock()->create());
- $this->assertNotEmpty($app->locks()->get($page));
-
- $this->assertTrue($page->lock()->remove());
- $this->assertEmpty($app->locks()->get($page));
- }
-
- public function testUnlockWithNoLock()
- {
- $app = $this->app;
- $page = $app->page('test');
- $app->impersonate('test@getkirby.com');
-
- $this->assertTrue($page->lock()->unlock());
- }
-
- public function testUnlock()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('test@getkirby.com');
- $this->assertTrue($page->lock()->create());
-
- $app->impersonate('homer@simpson.com');
- $this->assertTrue($page->lock()->unlock());
-
- $this->assertNotEmpty($app->locks()->get($page)['unlock']);
- }
-
- public function testIsUnlocked()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('test@getkirby.com');
- $this->assertTrue($page->lock()->create());
-
- $app->impersonate('homer@simpson.com');
- $this->assertTrue($page->lock()->unlock());
- $this->assertFalse($page->lock()->isUnlocked());
-
- $app->impersonate('test@getkirby.com');
- $this->assertTrue($page->lock()->isUnlocked());
- }
-
- public function testResolveWithNoUnlock()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('test@getkirby.com');
- $this->assertTrue($page->lock()->create());
-
- $app->impersonate('homer@simpson.com');
- $this->assertTrue($page->lock()->resolve());
- }
-
- public function testResolve()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('test@getkirby.com');
- $this->assertTrue($page->lock()->create());
-
- $app->impersonate('homer@simpson.com');
- $this->assertTrue($page->lock()->unlock());
- $this->assertNotEmpty($app->locks()->get($page)['unlock']);
-
- $app->impersonate('test@getkirby.com');
- $this->assertTrue($page->lock()->isUnlocked());
- $this->assertTrue($page->lock()->resolve());
- $this->assertFalse($page->lock()->isUnlocked());
- $this->assertArrayNotHasKey('unlock', $app->locks()->get($page));
- }
-
- public function testResolveWithRemainingUnlocks()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('test@getkirby.com');
- $this->assertTrue($page->lock()->create());
-
- $app->impersonate('homer@simpson.com');
- $this->assertTrue($page->lock()->unlock());
- $this->assertCount(1, $app->locks()->get($page)['unlock']);
- $this->assertTrue($page->lock()->create());
-
- $app->impersonate('peter@lustig.de');
- $this->assertTrue($page->lock()->unlock());
- $this->assertCount(2, $app->locks()->get($page)['unlock']);
-
- $app->impersonate('test@getkirby.com');
- $this->assertTrue($page->lock()->isUnlocked());
- $this->assertTrue($page->lock()->resolve());
- $this->assertFalse($page->lock()->isUnlocked());
- $this->assertCount(1, $app->locks()->get($page)['unlock']);
-
- $app->impersonate('homer@simpson.com');
- $this->assertTrue($page->lock()->isUnlocked());
- }
-
- public function testState()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('peter@lustig.de');
-
- $page->lock()->create();
-
- $this->assertNull($page->lock()->state());
-
- $app->impersonate('test@getkirby.com');
-
- // state is locked
- $this->assertSame('lock', $page->lock()->state());
-
- // user force unlocks the lock
- $page->lock()->unlock();
-
- $app->impersonate('peter@lustig.de');
-
- // state is now unlock for the original user
- $this->assertSame('unlock', $page->lock()->state());
- }
-
- public function testToArray()
- {
- $app = $this->app;
- $page = $app->page('test');
-
- $app->impersonate('peter@lustig.de');
-
- $page->lock()->create();
-
- $expected = [
- 'state' => null,
- 'data' => false
- ];
-
- $this->assertSame($expected, $page->lock()->toArray());
-
- $app->impersonate('test@getkirby.com');
-
- $lockArray = $page->lock()->toArray();
-
- // state is locked
- $this->assertSame('lock', $lockArray['state']);
- $this->assertSame('peter@lustig.de', $lockArray['data']['email']);
-
- // user force unlocks the lock
- $page->lock()->unlock();
-
- $app->impersonate('peter@lustig.de');
-
- $lockArray = $page->lock()->toArray();
-
- // state is locked
- $this->assertSame('unlock', $lockArray['state']);
- $this->assertFalse($lockArray['data']);
- }
-}
diff --git a/tests/Cms/Content/ContentLocksTest.php b/tests/Cms/Content/ContentLocksTest.php
deleted file mode 100644
index dae8210af5..0000000000
--- a/tests/Cms/Content/ContentLocksTest.php
+++ /dev/null
@@ -1,88 +0,0 @@
- [
- 'index' => static::TMP
- ],
- 'site' => [
- 'children' => [
- [
- 'slug' => 'test'
- ]
- ]
- ]
- ]);
- }
-
- public function setUp(): void
- {
- $this->app = $this->app();
- Dir::make(static::TMP);
- }
-
- public function tearDown(): void
- {
- Dir::remove(static::TMP);
- }
-
- public function testFile()
- {
- $app = $this->app;
- $page = $app->page('test');
- $this->assertTrue(Str::endsWith($app->locks()->file($page), 'content/test/.lock'));
- }
-
- public function testId()
- {
- $app = $this->app;
- $page = $app->page('test');
- $this->assertSame('/test', $app->locks()->id($page));
- }
-
- public function testGetSet()
- {
- $app = $this->app;
- $page = $app->page('test');
- $root = static::TMP . '/content/test';
-
- // create temp directory
- $this->assertSame($root . '/.lock', $app->locks()->file($page));
- Dir::make($root);
-
- // check if empty
- $this->assertSame([], $app->locks()->get($page));
- $this->assertFalse(F::exists($app->locks()->file($page)));
-
- // set data
- $this->assertTrue($app->locks()->set($page, [
- 'lock' => ['user' => 'homer'],
- 'unlock' => []
- ]));
-
- // check if exists
- $this->assertTrue(F::exists($app->locks()->file($page)));
- $this->assertSame([
- 'lock' => ['user' => 'homer']
- ], $app->locks()->get($page));
-
- // set null data
- $this->assertTrue($app->locks()->set($page, []));
-
- // check if empty
- $this->assertSame([], $app->locks()->get($page));
- $this->assertFalse(F::exists($app->locks()->file($page)));
- }
-}
diff --git a/tests/Cms/Files/FileTest.php b/tests/Cms/Files/FileTest.php
index ce3628a2f2..304b49b705 100644
--- a/tests/Cms/Files/FileTest.php
+++ b/tests/Cms/Files/FileTest.php
@@ -769,7 +769,7 @@ public function testPreviewUrlUnauthenticated()
]);
$file = $page->file('test.pdf');
- $this->assertNull($file->previewUrl());
+ $this->assertSame('/media/pages/test/' . $file->mediaHash() . '/test.pdf', $file->previewUrl());
}
public function testPreviewUrlForDraft()
@@ -852,7 +852,7 @@ public function testPreviewUrlForPageWithDeniedPreviewSetting()
$app->impersonate('test@getkirby.com');
$file = $app->file('test/test.pdf');
- $this->assertNull($file->previewUrl());
+ $this->assertSame('/media/pages/test/' . $file->mediaHash() . '/test.pdf', $file->previewUrl());
}
public function testPreviewUrlForPageWithCustomPreviewSetting()
diff --git a/tests/Cms/Models/ModelWithContentTest.php b/tests/Cms/Models/ModelWithContentTest.php
index 8b6a2dd66d..4c9c385213 100644
--- a/tests/Cms/Models/ModelWithContentTest.php
+++ b/tests/Cms/Models/ModelWithContentTest.php
@@ -3,6 +3,7 @@
namespace Kirby\Cms;
use Closure;
+use Kirby\Content\Lock;
use Kirby\Content\Version;
use Kirby\Content\VersionId;
use Kirby\Exception\NotFoundException;
@@ -10,6 +11,9 @@
use Kirby\Uuid\PageUuid;
use Kirby\Uuid\SiteUuid;
+/**
+ * @coversDefaultClass \Kirby\Cms\ModelWithContent
+ */
class ExtendedModelWithContent extends ModelWithContent
{
public function blueprint(): Blueprint
@@ -217,19 +221,6 @@ public function testContentUpdateWithMultipleLanguages()
$this->assertSame('Test', $page->content()->get('title')->value());
}
-
- public function testContentLock()
- {
- $model = new ExtendedModelWithContent();
- $this->assertInstanceOf(ContentLock::class, $model->lock());
- }
-
- public function testContentLockWithNoDirectory()
- {
- $model = new BrokenModelWithContent();
- $this->assertNull($model->lock());
- }
-
public function testContentWithChanges()
{
$app = new App([
@@ -247,20 +238,25 @@ public function testContentWithChanges()
$page = $app->page('foo');
- $this->assertSame(null, $page->content()->title()->value());
+ // create the latest version
+ $page->version('latest')->save([
+ 'title' => 'Original Title'
+ ]);
+
+ $this->assertSame('Original Title', $page->content()->title()->value());
// create some changes
$page->version('changes')->save([
- 'title' => 'Test'
+ 'title' => 'Changed Title'
]);
VersionId::$render = VersionId::changes();
- $this->assertSame('Test', $page->content()->title()->value());
+ $this->assertSame('Changed Title', $page->content()->title()->value());
VersionId::$render = null;
- $this->assertSame(null, $page->content()->title()->value());
+ $this->assertSame('Original Title', $page->content()->title()->value());
}
/**
@@ -313,6 +309,17 @@ public function testKirby()
$this->assertSame($kirby, $model->kirby());
}
+ public function testLock()
+ {
+ $page = new Page(['slug' => 'foo']);
+ $lock = $page->lock();
+
+ $this->assertInstanceOf(Lock::class, $lock);
+ $this->assertFalse($lock->isLocked());
+ $this->assertNull($lock->modified());
+ $this->assertNull($lock->user());
+ }
+
public function testSite()
{
$site = new Site();
diff --git a/tests/Cms/Pages/PageActionsTest.php b/tests/Cms/Pages/PageActionsTest.php
index 2df2ae79c8..93adef9199 100644
--- a/tests/Cms/Pages/PageActionsTest.php
+++ b/tests/Cms/Pages/PageActionsTest.php
@@ -905,8 +905,6 @@ public function testDuplicate()
$page = $this->app->site()->createChild([
'slug' => 'test',
]);
- $page->lock()->create();
- $this->assertFileExists($this->app->locks()->file($page));
// check UUID exists
$oldUuid = $page->content()->get('uuid')->value();
@@ -917,8 +915,6 @@ public function testDuplicate()
$copy = $page->duplicate('test-copy');
- $this->assertFileDoesNotExist(static::TMP . $copy->root() . '/.lock');
-
$this->assertIsPage($page, $drafts->find('test'));
$this->assertIsPage($page, $childrenAndDrafts->find('test'));
diff --git a/tests/Cms/Pages/PageCacheTest.php b/tests/Cms/Pages/PageCacheTest.php
deleted file mode 100644
index 73b8402cd6..0000000000
--- a/tests/Cms/Pages/PageCacheTest.php
+++ /dev/null
@@ -1,327 +0,0 @@
-app = new App([
- 'roots' => [
- 'index' => static::TMP,
- 'templates' => static::FIXTURES
- ],
- 'site' => [
- 'children' => [
- [
- 'slug' => 'default'
- ],
- [
- 'slug' => 'expiry',
- 'template' => 'expiry'
- ],
- [
- 'slug' => 'disabled',
- 'template' => 'disabled'
- ],
- [
- 'slug' => 'dynamic-auth',
- 'template' => 'dynamic'
- ],
- [
- 'slug' => 'dynamic-cookie',
- 'template' => 'dynamic'
- ],
- [
- 'slug' => 'dynamic-session',
- 'template' => 'dynamic'
- ],
- [
- 'slug' => 'dynamic-auth-session',
- 'template' => 'dynamic'
- ]
- ]
- ],
- 'options' => [
- 'cache.pages' => true
- ]
- ]);
-
- Dir::make(static::TMP);
- }
-
- public function tearDown(): void
- {
- Dir::remove(static::TMP);
-
- unset(
- $_COOKIE['foo'],
- $_COOKIE['kirby_session'],
- $_SERVER['HTTP_AUTHORIZATION']
- );
- }
-
- public static function requestMethodProvider(): array
- {
- return [
- ['GET', true],
- ['HEAD', true],
- ['POST', false],
- ['DELETE', false],
- ['PATCH', false],
- ['PUT', false],
- ];
- }
-
- /**
- * @dataProvider requestMethodProvider
- */
- public function testRequestMethod($method, $expected)
- {
- $app = $this->app->clone([
- 'request' => [
- 'method' => $method
- ]
- ]);
-
- $this->assertSame($expected, $app->page('default')->isCacheable());
- }
-
- /**
- * @dataProvider requestMethodProvider
- */
- public function testRequestData($method)
- {
- $app = $this->app->clone([
- 'request' => [
- 'method' => $method,
- 'query' => ['foo' => 'bar']
- ]
- ]);
-
- $this->assertFalse($app->page('default')->isCacheable());
- }
-
- public function testRequestParams()
- {
- $app = $this->app->clone([
- 'request' => [
- 'url' => 'https://getkirby.com/blog/page:2'
- ]
- ]);
-
- $this->assertFalse($app->page('default')->isCacheable());
- }
-
- public function testIgnoreId()
- {
- $app = $this->app->clone([
- 'options' => [
- 'cache.pages' => [
- 'ignore' => [
- 'expiry'
- ]
- ]
- ]
- ]);
-
- $this->assertTrue($app->page('default')->isCacheable());
- $this->assertFalse($app->page('expiry')->isCacheable());
- }
-
- public function testIgnoreCallback()
- {
- $app = $this->app->clone([
- 'options' => [
- 'cache.pages' => [
- 'ignore' => fn ($page) => $page->id() === 'default'
- ]
- ]
- ]);
-
- $this->assertFalse($app->page('default')->isCacheable());
- $this->assertTrue($app->page('expiry')->isCacheable());
- }
-
- public function testDisabledCache()
- {
- // deactivate on top level
- $app = $this->app->clone([
- 'options' => [
- 'cache.pages' => false
- ]
- ]);
-
- $this->assertFalse($app->page('default')->isCacheable());
-
- // deactivate in array
- $app = $this->app->clone([
- 'options' => [
- 'cache.pages' => [
- 'active' => false
- ]
- ]
- ]);
-
- $this->assertFalse($app->page('default')->isCacheable());
- }
-
- public function testRenderCache()
- {
- $cache = $this->app->cache('pages');
- $page = $this->app->page('default');
-
- $this->assertNull($cache->retrieve('default.html'));
-
- $html1 = $page->render();
- $this->assertStringStartsWith('This is a test:', $html1);
-
- $value = $cache->retrieve('default.html');
- $this->assertInstanceOf(Value::class, $value);
- $this->assertSame($html1, $value->value()['html']);
- $this->assertNull($value->expires());
-
- $html2 = $page->render();
- $this->assertSame($html1, $html2);
- }
-
- public function testRenderCacheCustomExpiry()
- {
- $cache = $this->app->cache('pages');
- $page = $this->app->page('expiry');
-
- $this->assertNull($cache->retrieve('expiry.html'));
-
- $time = $page->render();
-
- $value = $cache->retrieve('expiry.html');
- $this->assertInstanceOf(Value::class, $value);
- $this->assertSame($time, $value->value()['html']);
- $this->assertSame((int)$time, $value->expires());
- }
-
- public function testRenderCacheDisabled()
- {
- $cache = $this->app->cache('pages');
- $page = $this->app->page('disabled');
-
- $this->assertNull($cache->retrieve('disabled.html'));
-
- $html1 = $page->render();
- $this->assertStringStartsWith('This is a test:', $html1);
-
- $this->assertNull($cache->retrieve('disabled.html'));
-
- $html2 = $page->render();
- $this->assertNotSame($html1, $html2);
- }
-
- public static function dynamicProvider(): array
- {
- return [
- ['dynamic-auth', ['auth']],
- ['dynamic-cookie', ['cookie']],
- ['dynamic-session', ['session']],
- ['dynamic-auth-session', ['auth', 'session']],
- ];
- }
-
- /**
- * @dataProvider dynamicProvider
- */
- public function testRenderCacheDynamicNonActive(string $slug, array $dynamicElements)
- {
- $cache = $this->app->cache('pages');
- $page = $this->app->page($slug);
-
- $this->assertNull($cache->retrieve($slug . '.html'));
-
- $html1 = $page->render();
- $this->assertStringStartsWith('This is a test:', $html1);
-
- $cacheValue = $cache->retrieve($slug . '.html');
- $this->assertNotNull($cacheValue);
- $this->assertSame(in_array('auth', $dynamicElements), $cacheValue->value()['usesAuth']);
- if (in_array('cookie', $dynamicElements)) {
- $this->assertSame(['foo'], $cacheValue->value()['usesCookies']);
- } elseif (in_array('session', $dynamicElements)) {
- $this->assertSame(['kirby_session'], $cacheValue->value()['usesCookies']);
- } else {
- $this->assertSame([], $cacheValue->value()['usesCookies']);
- }
-
- // reset the Kirby Responder object
- $this->setUp();
- $html2 = $page->render();
- $this->assertSame($html1, $html2);
- }
-
- /**
- * @dataProvider dynamicProvider
- */
- public function testRenderCacheDynamicActiveOnFirstRender(string $slug, array $dynamicElements)
- {
- $_COOKIE['foo'] = $_COOKIE['kirby_session'] = 'bar';
- $this->app->clone([
- 'server' => [
- 'HTTP_AUTHORIZATION' => 'Bearer brown-bearer'
- ]
- ]);
-
- $cache = $this->app->cache('pages');
- $page = $this->app->page($slug);
-
- $this->assertNull($cache->retrieve($slug . '.html'));
-
- $html1 = $page->render();
- $this->assertStringStartsWith('This is a test:', $html1);
-
- $cacheValue = $cache->retrieve($slug . '.html');
- $this->assertNull($cacheValue);
-
- // reset the Kirby Responder object
- $this->setUp();
- $html2 = $page->render();
- $this->assertNotSame($html1, $html2);
- }
-
- /**
- * @dataProvider dynamicProvider
- */
- public function testRenderCacheDynamicActiveOnSecondRender(string $slug, array $dynamicElements)
- {
- $cache = $this->app->cache('pages');
- $page = $this->app->page($slug);
-
- $this->assertNull($cache->retrieve($slug . '.html'));
-
- $html1 = $page->render();
- $this->assertStringStartsWith('This is a test:', $html1);
-
- $cacheValue = $cache->retrieve($slug . '.html');
- $this->assertNotNull($cacheValue);
- $this->assertSame(in_array('auth', $dynamicElements), $cacheValue->value()['usesAuth']);
- if (in_array('cookie', $dynamicElements)) {
- $this->assertSame(['foo'], $cacheValue->value()['usesCookies']);
- } elseif (in_array('session', $dynamicElements)) {
- $this->assertSame(['kirby_session'], $cacheValue->value()['usesCookies']);
- } else {
- $this->assertSame([], $cacheValue->value()['usesCookies']);
- }
-
- $_COOKIE['foo'] = $_COOKIE['kirby_session'] = 'bar';
- $_SERVER['HTTP_AUTHORIZATION'] = 'Bearer brown-bearer';
-
- // reset the Kirby Responder object
- $this->setUp();
- $html2 = $page->render();
- $this->assertNotSame($html1, $html2);
- }
-}
diff --git a/tests/Cms/Pages/PageRenderTest.php b/tests/Cms/Pages/PageRenderTest.php
new file mode 100644
index 0000000000..b267fffd9d
--- /dev/null
+++ b/tests/Cms/Pages/PageRenderTest.php
@@ -0,0 +1,916 @@
+app = new App([
+ 'roots' => [
+ 'index' => static::TMP,
+ 'controllers' => static::FIXTURES . '/controllers',
+ 'templates' => static::FIXTURES . '/templates'
+ ],
+ 'site' => [
+ 'children' => [
+ [
+ 'slug' => 'default',
+ 'template' => 'cache-default'
+ ],
+ [
+ 'slug' => 'data',
+ 'template' => 'cache-data'
+ ],
+ [
+ 'slug' => 'expiry',
+ 'template' => 'cache-expiry'
+ ],
+ [
+ 'slug' => 'metadata',
+ 'template' => 'cache-metadata',
+ ],
+ [
+ 'slug' => 'disabled',
+ 'template' => 'cache-disabled'
+ ],
+ [
+ 'slug' => 'dynamic-auth',
+ 'template' => 'cache-dynamic'
+ ],
+ [
+ 'slug' => 'dynamic-cookie',
+ 'template' => 'cache-dynamic'
+ ],
+ [
+ 'slug' => 'dynamic-session',
+ 'template' => 'cache-dynamic'
+ ],
+ [
+ 'slug' => 'dynamic-auth-session',
+ 'template' => 'cache-dynamic'
+ ],
+ [
+ 'slug' => 'representation',
+ 'template' => 'representation'
+ ],
+ [
+ 'slug' => 'invalid',
+ 'template' => 'invalid',
+ ],
+ [
+ 'slug' => 'controller',
+ 'template' => 'controller',
+ ],
+ [
+ 'slug' => 'bar',
+ 'template' => 'hook-bar',
+ 'content' => [
+ 'title' => 'Bar Title',
+ ]
+ ],
+ [
+ 'slug' => 'foo',
+ 'template' => 'hook-foo',
+ 'content' => [
+ 'title' => 'Foo Title',
+ ]
+ ],
+ [
+ 'slug' => 'version',
+ 'template' => 'version'
+ ],
+ [
+ 'slug' => 'version-exception',
+ 'template' => 'version-exception'
+ ],
+ [
+ 'slug' => 'version-recursive',
+ 'template' => 'version-recursive'
+ ]
+ ],
+ 'drafts' => [
+ [
+ 'slug' => 'version-draft',
+ 'template' => 'version',
+ 'children' => [
+ [
+ 'slug' => 'a-child',
+ 'template' => 'version'
+ ]
+ ]
+ ],
+ ]
+ ],
+ 'options' => [
+ 'cache.pages' => true
+ ]
+ ]);
+
+ Dir::make(static::TMP);
+ }
+
+ public function tearDown(): void
+ {
+ Dir::remove(static::TMP);
+
+ unset(
+ $_COOKIE['foo'],
+ $_COOKIE['kirby_session'],
+ $_SERVER['HTTP_AUTHORIZATION']
+ );
+ }
+
+ public static function requestMethodProvider(): array
+ {
+ return [
+ ['GET', true],
+ ['HEAD', true],
+ ['POST', false],
+ ['DELETE', false],
+ ['PATCH', false],
+ ['PUT', false],
+ ];
+ }
+
+ /**
+ * @covers ::isCacheable
+ * @dataProvider requestMethodProvider
+ */
+ public function testIsCacheableRequestMethod($method, $expected)
+ {
+ $app = $this->app->clone([
+ 'request' => [
+ 'method' => $method
+ ]
+ ]);
+
+ $this->assertSame($expected, $app->page('default')->isCacheable());
+ $this->assertSame($expected, $app->page('default')->isCacheable(VersionId::latest()));
+ $this->assertFalse($app->page('default')->isCacheable(VersionId::changes()));
+ }
+
+ /**
+ * @covers ::isCacheable
+ * @dataProvider requestMethodProvider
+ */
+ public function testIsCacheableRequestData($method)
+ {
+ $app = $this->app->clone([
+ 'request' => [
+ 'method' => $method,
+ 'query' => ['foo' => 'bar']
+ ]
+ ]);
+
+ $this->assertFalse($app->page('default')->isCacheable());
+ }
+
+ /**
+ * @covers ::isCacheable
+ */
+ public function testIsCacheableRequestParams()
+ {
+ $app = $this->app->clone([
+ 'request' => [
+ 'url' => 'https://getkirby.com/blog/page:2'
+ ]
+ ]);
+
+ $this->assertFalse($app->page('default')->isCacheable());
+ }
+
+ /**
+ * @covers ::isCacheable
+ */
+ public function testIsCacheableIgnoreId()
+ {
+ $app = $this->app->clone([
+ 'options' => [
+ 'cache.pages' => [
+ 'ignore' => [
+ 'data'
+ ]
+ ]
+ ]
+ ]);
+
+ $this->assertTrue($app->page('default')->isCacheable());
+ $this->assertTrue($app->page('default')->isCacheable(VersionId::latest()));
+ $this->assertFalse($app->page('default')->isCacheable(VersionId::changes()));
+ $this->assertFalse($app->page('data')->isCacheable());
+ $this->assertFalse($app->page('data')->isCacheable(VersionId::latest()));
+ $this->assertFalse($app->page('data')->isCacheable(VersionId::changes()));
+ }
+
+ /**
+ * @covers ::isCacheable
+ */
+ public function testIsCacheableIgnoreCallback()
+ {
+ $app = $this->app->clone([
+ 'options' => [
+ 'cache.pages' => [
+ 'ignore' => fn ($page) => $page->id() === 'default'
+ ]
+ ]
+ ]);
+
+ $this->assertFalse($app->page('default')->isCacheable());
+ $this->assertFalse($app->page('default')->isCacheable(VersionId::latest()));
+ $this->assertFalse($app->page('default')->isCacheable(VersionId::changes()));
+ $this->assertTrue($app->page('data')->isCacheable());
+ $this->assertTrue($app->page('data')->isCacheable(VersionId::latest()));
+ $this->assertFalse($app->page('data')->isCacheable(VersionId::changes()));
+ }
+
+ /**
+ * @covers ::isCacheable
+ */
+ public function testIsCacheableDisabledCache()
+ {
+ // deactivate on top level
+ $app = $this->app->clone([
+ 'options' => [
+ 'cache.pages' => false
+ ]
+ ]);
+
+ $this->assertFalse($app->page('default')->isCacheable());
+
+ // deactivate in array
+ $app = $this->app->clone([
+ 'options' => [
+ 'cache.pages' => [
+ 'active' => false
+ ]
+ ]
+ ]);
+
+ $this->assertFalse($app->page('default')->isCacheable());
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderCache()
+ {
+ $cache = $this->app->cache('pages');
+ $page = $this->app->page('default');
+
+ $this->assertNull($cache->retrieve('default.latest.html'));
+
+ $html1 = $page->render();
+ $this->assertStringStartsWith('This is a test:', $html1);
+
+ $value = $cache->retrieve('default.latest.html');
+ $this->assertInstanceOf(Value::class, $value);
+ $this->assertSame($html1, $value->value()['html']);
+ $this->assertNull($value->expires());
+
+ $html2 = $page->render();
+ $this->assertSame($html1, $html2);
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderCacheCustomExpiry()
+ {
+ $cache = $this->app->cache('pages');
+ $page = $this->app->page('expiry');
+
+ $this->assertNull($cache->retrieve('expiry.latest.html'));
+
+ $time = $page->render();
+
+ $value = $cache->retrieve('expiry.latest.html');
+ $this->assertInstanceOf(Value::class, $value);
+ $this->assertSame($time, $value->value()['html']);
+ $this->assertSame((int)$time, $value->expires());
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderCacheMetadata()
+ {
+ $cache = $this->app->cache('pages');
+ $page = $this->app->page('metadata');
+
+ $this->assertNull($cache->retrieve('metadata.latest.html'));
+
+ $html1 = $page->render();
+ $this->assertStringStartsWith('This is a test:', $html1);
+ $this->assertSame(202, $this->app->response()->code());
+ $this->assertSame(['Cache-Control' => 'private'], $this->app->response()->headers());
+ $this->assertSame('text/plain', $this->app->response()->type());
+
+ // reset the Kirby Responder object
+ $this->setUp();
+ $this->assertNull($this->app->response()->code());
+ $this->assertSame([], $this->app->response()->headers());
+ $this->assertNull($this->app->response()->type());
+
+ // ensure the Responder object is restored from cache
+ $html2 = $this->app->page('metadata')->render();
+ $this->assertSame($html1, $html2);
+ $this->assertSame(202, $this->app->response()->code());
+ $this->assertSame(['Cache-Control' => 'private'], $this->app->response()->headers());
+ $this->assertSame('text/plain', $this->app->response()->type());
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderCacheDisabled()
+ {
+ $cache = $this->app->cache('pages');
+ $page = $this->app->page('disabled');
+
+ $this->assertNull($cache->retrieve('disabled.latest.html'));
+
+ $html1 = $page->render();
+ $this->assertStringStartsWith('This is a test:', $html1);
+
+ $this->assertNull($cache->retrieve('disabled.latest.html'));
+
+ $html2 = $page->render();
+ $this->assertStringStartsWith('This is a test:', $html2);
+ $this->assertNotSame($html1, $html2);
+ }
+
+ public static function dynamicProvider(): array
+ {
+ return [
+ ['dynamic-auth', ['auth']],
+ ['dynamic-cookie', ['cookie']],
+ ['dynamic-session', ['session']],
+ ['dynamic-auth-session', ['auth', 'session']],
+ ];
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ * @dataProvider dynamicProvider
+ */
+ public function testRenderCacheDynamicNonActive(string $slug, array $dynamicElements)
+ {
+ $cache = $this->app->cache('pages');
+ $page = $this->app->page($slug);
+
+ $this->assertNull($cache->retrieve($slug . '.latest.html'));
+
+ $html1 = $page->render();
+ $this->assertStringStartsWith('This is a test:', $html1);
+
+ $cacheValue = $cache->retrieve($slug . '.latest.html');
+ $this->assertNotNull($cacheValue);
+ $this->assertSame(in_array('auth', $dynamicElements), $cacheValue->value()['usesAuth']);
+ if (in_array('cookie', $dynamicElements)) {
+ $this->assertSame(['foo'], $cacheValue->value()['usesCookies']);
+ } elseif (in_array('session', $dynamicElements)) {
+ $this->assertSame(['kirby_session'], $cacheValue->value()['usesCookies']);
+ } else {
+ $this->assertSame([], $cacheValue->value()['usesCookies']);
+ }
+
+ // reset the Kirby Responder object
+ $this->setUp();
+ $html2 = $page->render();
+ $this->assertSame($html1, $html2);
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ * @dataProvider dynamicProvider
+ */
+ public function testRenderCacheDynamicActiveOnFirstRender(string $slug, array $dynamicElements)
+ {
+ $_COOKIE['foo'] = $_COOKIE['kirby_session'] = 'bar';
+ $this->app->clone([
+ 'server' => [
+ 'HTTP_AUTHORIZATION' => 'Bearer brown-bearer'
+ ]
+ ]);
+
+ $cache = $this->app->cache('pages');
+ $page = $this->app->page($slug);
+
+ $this->assertNull($cache->retrieve($slug . '.latest.html'));
+
+ $html1 = $page->render();
+ $this->assertStringStartsWith('This is a test:', $html1);
+
+ $cacheValue = $cache->retrieve($slug . '.latest.html');
+ $this->assertNull($cacheValue);
+
+ // reset the Kirby Responder object
+ $this->setUp();
+ $html2 = $page->render();
+ $this->assertStringStartsWith('This is a test:', $html2);
+ $this->assertNotSame($html1, $html2);
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ * @dataProvider dynamicProvider
+ */
+ public function testRenderCacheDynamicActiveOnSecondRender(string $slug, array $dynamicElements)
+ {
+ $cache = $this->app->cache('pages');
+ $page = $this->app->page($slug);
+
+ $this->assertNull($cache->retrieve($slug . '.latest.html'));
+
+ $html1 = $page->render();
+ $this->assertStringStartsWith('This is a test:', $html1);
+
+ $cacheValue = $cache->retrieve($slug . '.latest.html');
+ $this->assertNotNull($cacheValue);
+ $this->assertSame(in_array('auth', $dynamicElements), $cacheValue->value()['usesAuth']);
+ if (in_array('cookie', $dynamicElements)) {
+ $this->assertSame(['foo'], $cacheValue->value()['usesCookies']);
+ } elseif (in_array('session', $dynamicElements)) {
+ $this->assertSame(['kirby_session'], $cacheValue->value()['usesCookies']);
+ } else {
+ $this->assertSame([], $cacheValue->value()['usesCookies']);
+ }
+
+ $_COOKIE['foo'] = $_COOKIE['kirby_session'] = 'bar';
+ $_SERVER['HTTP_AUTHORIZATION'] = 'Bearer brown-bearer';
+
+ // reset the Kirby Responder object
+ $this->setUp();
+ $html2 = $page->render();
+ $this->assertStringStartsWith('This is a test:', $html2);
+ $this->assertNotSame($html1, $html2);
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderCacheDataInitial()
+ {
+ $cache = $this->app->cache('pages');
+ $page = $this->app->page('data');
+
+ $this->assertNull($cache->retrieve('data.latest.html'));
+
+ $html = $page->render(['test' => 'custom test']);
+ $this->assertStringStartsWith('This is a custom test:', $html);
+
+ $this->assertNull($cache->retrieve('data.latest.html'));
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderCacheDataPreCached()
+ {
+ $cache = $this->app->cache('pages');
+ $page = $this->app->page('data');
+
+ $this->assertNull($cache->retrieve('data.latest.html'));
+
+ $html1 = $page->render();
+ $this->assertStringStartsWith('This is a test:', $html1);
+
+ $value = $cache->retrieve('data.latest.html');
+ $this->assertInstanceOf(Value::class, $value);
+ $this->assertSame($html1, $value->value()['html']);
+ $this->assertNull($value->expires());
+
+ $html2 = $page->render(['test' => 'custom test']);
+ $this->assertStringStartsWith('This is a custom test:', $html2);
+
+ // cache still stores the non-custom result
+ $value = $cache->retrieve('data.latest.html');
+ $this->assertInstanceOf(Value::class, $value);
+ $this->assertSame($html1, $value->value()['html']);
+ $this->assertNull($value->expires());
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderRepresentationDefault()
+ {
+ $page = $this->app->page('representation');
+
+ $this->assertSame('Some HTML: representation', $page->render());
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderRepresentationOverride()
+ {
+ $page = $this->app->page('representation');
+
+ $this->assertSame('Some HTML: representation', $page->render(contentType: 'html'));
+ $this->assertSame('{"some json": "representation"}', $page->render(contentType: 'json'));
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderRepresentationMissing()
+ {
+ $this->expectException(NotFoundException::class);
+ $this->expectExceptionMessage('The content representation cannot be found');
+
+ $page = $this->app->page('representation');
+ $page->render(contentType: 'txt');
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderTemplateMissing()
+ {
+ $this->expectException(NotFoundException::class);
+ $this->expectExceptionMessage('The default template does not exist');
+
+ $page = $this->app->page('invalid');
+ $page->render();
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderController()
+ {
+ $page = $this->app->page('controller');
+
+ $this->assertSame('Data says TEST: controller and default!', $page->render());
+ $this->assertSame('Data says TEST: controller and custom!', $page->render(['test' => 'override', 'test2' => 'custom']));
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderHookBefore()
+ {
+ $app = $this->app->clone([
+ 'hooks' => [
+ 'page.render:before' => function ($contentType, $data, $page) {
+ $data['bar'] = 'Test';
+ return $data;
+ }
+ ]
+ ]);
+
+ $page = $app->page('bar');
+ $this->assertSame('Bar Title : Test', $page->render());
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderHookAfter()
+ {
+ $app = $this->app->clone([
+ 'hooks' => [
+ 'page.render:after' => function ($contentType, $data, $html, $page) {
+ return str_replace(':', '-', $html);
+ }
+ ]
+ ]);
+
+ $page = $app->page('foo');
+ $this->assertSame('foo - Foo Title', $page->render());
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderVersionDetectedFromRequest()
+ {
+ $page = $this->app->page('version');
+ $page->version('latest')->save(['title' => 'Latest Title']);
+ $page->version('changes')->save(['title' => 'Changes Title']);
+
+ $this->assertSame("Version: latest\nContent: Latest Title", $page->render());
+
+ $this->app = $this->app->clone([
+ 'request' => [
+ 'query' => [
+ '_token' => $page->version('changes')->previewToken(),
+ '_version' => 'changes'
+ ]
+ ]
+ ]);
+
+ $this->assertSame("Version: changes\nContent: Changes Title", $page->render());
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderVersionDetectedFromRequestDraft()
+ {
+ $page = $this->app->page('version-draft');
+ $page->version('latest')->save(['title' => 'Latest Title']);
+ $page->version('changes')->save(['title' => 'Changes Title']);
+
+ // manual renders of drafts falls back to the latest version even if
+ // the draft couldn't be rendered "publicly" by `$kirby->resolve()`
+ $this->assertSame("Version: latest\nContent: Latest Title", $page->render());
+
+ $this->app = $this->app->clone([
+ 'request' => [
+ 'query' => [
+ '_token' => $page->version('changes')->previewToken(),
+ '_version' => 'changes'
+ ]
+ ]
+ ]);
+
+ $this->assertSame("Version: changes\nContent: Changes Title", $page->render());
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderVersionDetectedRecursive()
+ {
+ $versionPage = $this->app->page('version');
+ $versionPage->version('latest')->save(['title' => 'Latest Title']);
+ $versionPage->version('changes')->save(['title' => 'Changes Title']);
+
+ $page = $this->app->page('version-recursive');
+
+ $this->assertSame("\nVersion: latest\nContent: Latest Title\n", $page->render());
+ $this->assertSame("\nVersion: latest\nContent: Latest Title\n", $page->render(versionId: 'latest'));
+
+ // the overridden version propagates to the lower level
+ $this->assertSame("\nVersion: changes\nContent: Changes Title\n", $page->render(versionId: 'changes'));
+
+ // even if the request says something else
+ $this->app = $this->app->clone([
+ 'request' => [
+ 'query' => ['_version' => 'changes']
+ ]
+ ]);
+
+ $this->assertSame("\nVersion: latest\nContent: Latest Title\n", $page->render(versionId: 'latest'));
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderVersionManual()
+ {
+ $page = $this->app->page('version');
+ $page->version('latest')->save(['title' => 'Latest Title']);
+ $page->version('changes')->save(['title' => 'Changes Title']);
+
+ $this->assertSame("Version: latest\nContent: Latest Title", $page->render(versionId: 'latest'));
+ $this->assertSame("Version: latest\nContent: Latest Title", $page->render(versionId: VersionId::latest()));
+ $this->assertSame("Version: changes\nContent: Changes Title", $page->render(versionId: 'changes'));
+ $this->assertSame("Version: changes\nContent: Changes Title", $page->render(versionId: VersionId::changes()));
+
+ $this->assertNull(VersionId::$render);
+ }
+
+ /**
+ * @covers ::cacheId
+ * @covers ::render
+ */
+ public function testRenderVersionException()
+ {
+ $page = $this->app->page('version-exception');
+
+ try {
+ $page->render(versionId: 'changes');
+ } catch (Exception) {
+ // exception itself is not relevant for this test
+ }
+
+ // global state always needs to be reset after rendering
+ $this->assertNull(VersionId::$render);
+ }
+
+ /**
+ * @covers ::renderVersionFromRequest
+ */
+ public function testRenderVersionFromRequestAuthenticated()
+ {
+ $page = $this->app->page('default');
+
+ $this->app->clone([
+ 'request' => [
+ 'query' => [
+ '_token' => $page->version('latest')->previewToken(),
+ '_version' => 'latest'
+ ]
+ ]
+ ]);
+
+ $this->assertSame('latest', $page->renderVersionFromRequest()->value());
+
+ $this->app->clone([
+ 'request' => [
+ 'query' => [
+ '_token' => $page->version('changes')->previewToken(),
+ '_version' => 'changes'
+ ]
+ ]
+ ]);
+
+ $this->assertSame('changes', $page->renderVersionFromRequest()->value());
+ }
+
+ /**
+ * @covers ::renderVersionFromRequest
+ */
+ public function testRenderVersionFromRequestAuthenticatedDraft()
+ {
+ $page = $this->app->page('version-draft');
+
+ $this->app->clone([
+ 'request' => [
+ 'query' => [
+ '_token' => $page->version('latest')->previewToken(),
+ '_version' => 'latest'
+ ]
+ ]
+ ]);
+
+ $this->assertSame('latest', $page->renderVersionFromRequest()->value());
+
+ $this->app->clone([
+ 'request' => [
+ 'query' => [
+ '_token' => $page->version('changes')->previewToken(),
+ '_version' => 'changes'
+ ]
+ ]
+ ]);
+
+ $this->assertSame('changes', $page->renderVersionFromRequest()->value());
+ }
+
+ /**
+ * @covers ::renderVersionFromRequest
+ */
+ public function testRenderVersionFromRequestMismatch()
+ {
+ $page = $this->app->page('default');
+
+ $this->app->clone([
+ 'request' => [
+ 'query' => [
+ '_token' => $page->version('changes')->previewToken(),
+ '_version' => 'latest'
+ ]
+ ]
+ ]);
+
+ $this->assertSame('latest', $page->renderVersionFromRequest()->value());
+
+ $this->app->clone([
+ 'request' => [
+ 'query' => [
+ '_token' => $page->version('latest')->previewToken(),
+ '_version' => 'changes'
+ ]
+ ]
+ ]);
+
+ $this->assertSame('latest', $page->renderVersionFromRequest()->value());
+ }
+
+ /**
+ * @covers ::renderVersionFromRequest
+ */
+ public function testRenderVersionFromRequestInvalidId()
+ {
+ $page = $this->app->page('default');
+ $draft = $this->app->page('version-draft');
+ $draftChild = $this->app->page('version-draft/a-child');
+
+ $this->app->clone([
+ 'request' => [
+ 'query' => [
+ '_token' => $page->version('changes')->previewToken(),
+ '_version' => 'some-gibberish'
+ ]
+ ]
+ ]);
+
+ $this->assertSame('latest', $page->renderVersionFromRequest()->value());
+ $this->assertNull($draft->renderVersionFromRequest());
+ $this->assertNull($draftChild->renderVersionFromRequest());
+ }
+
+ /**
+ * @covers ::renderVersionFromRequest
+ */
+ public function testRenderVersionFromRequestMissingId()
+ {
+ $page = $this->app->page('default');
+
+ $this->app->clone([
+ 'request' => [
+ 'query' => [
+ '_token' => $page->version('changes')->previewToken()
+ ]
+ ]
+ ]);
+
+ $this->assertSame('latest', $page->renderVersionFromRequest()->value());
+ }
+
+ /**
+ * @covers ::renderVersionFromRequest
+ */
+ public function testRenderVersionFromRequestMissingToken()
+ {
+ $page = $this->app->page('default');
+ $draft = $this->app->page('version-draft');
+ $draftChild = $this->app->page('version-draft/a-child');
+
+ $this->app->clone([
+ 'request' => [
+ 'query' => [
+ '_version' => 'changes'
+ ]
+ ]
+ ]);
+
+ $this->assertSame('latest', $page->renderVersionFromRequest()->value());
+ $this->assertNull($draft->renderVersionFromRequest());
+ $this->assertNull($draftChild->renderVersionFromRequest());
+
+ $this->app->clone([
+ 'request' => [
+ 'query' => [
+ '_token' => '',
+ '_version' => 'changes'
+ ]
+ ]
+ ]);
+
+ $this->assertSame('latest', $page->renderVersionFromRequest()->value());
+ $this->assertNull($draft->renderVersionFromRequest());
+ $this->assertNull($draftChild->renderVersionFromRequest());
+ }
+
+ /**
+ * @covers ::renderVersionFromRequest
+ */
+ public function testRenderVersionFromRequestInvalidToken()
+ {
+ $page = $this->app->page('default');
+ $draft = $this->app->page('version-draft');
+ $draftChild = $this->app->page('version-draft/a-child');
+
+ $this->app->clone([
+ 'request' => [
+ 'query' => [
+ '_token' => 'some-gibberish',
+ '_version' => 'changes'
+ ]
+ ]
+ ]);
+
+ $this->assertSame('latest', $page->renderVersionFromRequest()->value());
+ $this->assertNull($draft->renderVersionFromRequest());
+ $this->assertNull($draftChild->renderVersionFromRequest());
+ }
+}
diff --git a/tests/Cms/Pages/PageTest.php b/tests/Cms/Pages/PageTest.php
index 868f043624..6e2714b538 100644
--- a/tests/Cms/Pages/PageTest.php
+++ b/tests/Cms/Pages/PageTest.php
@@ -5,7 +5,6 @@
use Kirby\Exception\InvalidArgumentException;
use Kirby\Filesystem\F;
use Kirby\Panel\Page as Panel;
-use ReflectionMethod;
use TypeError;
class PageTestModel extends Page
@@ -608,18 +607,18 @@ public function testPreviewUrlUnauthenticated()
public static function previewUrlProvider(): array
{
return [
- [null, '/test', false],
- [null, '/test?{token}', true],
- [true, '/test', false],
- [true, '/test?{token}', true],
- ['/something/different', '/something/different', false],
- ['/something/different', '/something/different?{token}', true],
- ['{{ site.url }}#{{ page.slug }}', '/#test', false],
- ['{{ site.url }}#{{ page.slug }}', '/?{token}#test', true],
- ['{{ page.url }}?preview=true', '/test?preview=true&{token}', true],
- [false, null, false],
- [false, null, true],
- [null, null, false, false],
+ [null, '/test', null, false],
+ [null, '/test?{token}', 'test', true],
+ [true, '/test', null, false],
+ [true, '/test?{token}', 'test', true],
+ ['/something/different', '/something/different', null, false],
+ ['/something/different', '/something/different?{token}', 'something\/different', true],
+ ['{{ site.url }}#{{ page.slug }}', '/#test', null, false],
+ ['{{ site.url }}#{{ page.slug }}', '/?{token}#test', '', true],
+ ['{{ page.url }}?preview=true', '/test?preview=true&{token}', 'test', true],
+ [false, null, null, false],
+ [false, null, null, true],
+ [null, null, null, false, false],
];
}
@@ -629,6 +628,7 @@ public static function previewUrlProvider(): array
public function testCustomPreviewUrl(
$input,
$expected,
+ $expectedUri,
bool $draft,
bool $authenticated = true
): void {
@@ -678,9 +678,10 @@ public function testCustomPreviewUrl(
]);
if ($draft === true && $expected !== null) {
+ $expectedToken = substr(hash_hmac('sha1', '{"uri":"' . $expectedUri . '","versionId":"latest"}', $page->kirby()->root('content')), 0, 10);
$expected = str_replace(
'{token}',
- 'token=' . hash_hmac('sha1', $page->id() . $page->template(), $page->kirby()->root('content') . '/' . $page->id()),
+ '_token=' . $expectedToken,
$expected
);
}
@@ -694,83 +695,6 @@ public function testSlug()
$this->assertSame('test', $page->slug());
}
- public function testToken()
- {
- $app = new App([
- 'roots' => [
- 'index' => '/dev/null'
- ]
- ]);
-
- $page = new Page([
- 'slug' => 'test',
- 'template' => 'default'
- ]);
-
- $method = new ReflectionMethod(Page::class, 'token');
- $method->setAccessible(true);
-
- $expected = hash_hmac(
- 'sha1',
- 'testdefault',
- '/dev/null/content/test'
- );
- $this->assertSame($expected, $method->invoke($page));
- }
-
- public function testTokenWithCustomSalt()
- {
- new App([
- 'roots' => [
- 'index' => '/dev/null'
- ],
- 'options' => [
- 'content' => [
- 'salt' => 'testsalt'
- ]
- ]
- ]);
-
- $page = new Page([
- 'slug' => 'test',
- 'template' => 'default'
- ]);
-
- $method = new ReflectionMethod(Page::class, 'token');
- $method->setAccessible(true);
-
- $expected = hash_hmac('sha1', 'test' . 'default', 'testsalt');
- $this->assertSame($expected, $method->invoke($page));
- }
-
- public function testTokenWithSaltCallback()
- {
- new App([
- 'roots' => [
- 'index' => '/dev/null'
- ],
- 'options' => [
- 'content' => [
- 'salt' => fn ($page) => $page->date()
- ]
- ]
- ]);
-
- $page = new Page([
- 'slug' => 'test',
- 'template' => 'default',
- 'content' => [
- 'date' => '2012-12-12'
- ]
- ]);
-
- $method = new ReflectionMethod(Page::class, 'token');
- $method->setAccessible(true);
-
- $expected = hash_hmac('sha1', 'test' . 'default', '2012-12-12');
- $this->assertSame($expected, $method->invoke($page));
- }
-
public function testToString()
{
$page = new Page(['slug' => 'test']);
@@ -1138,67 +1062,4 @@ public function testToArray()
$this->assertSame($expected, $page->toArray());
}
-
- public function testRenderBeforeHook()
- {
- $app = new App([
- 'roots' => [
- 'index' => static::TMP
- ],
- 'templates' => [
- 'bar' => static::FIXTURES . '/PageRenderHookTest/bar.php'
- ],
- 'site' => [
- 'children' => [
- [
- 'slug' => 'bar',
- 'template' => 'bar',
- 'content' => [
- 'title' => 'Bar Title',
- ]
- ]
- ],
- ],
- 'hooks' => [
- 'page.render:before' => function ($contentType, $data, $page) {
- $data['bar'] = 'Test';
- return $data;
- }
- ]
- ]);
-
- $page = $app->page('bar');
- $this->assertSame('Bar Title : Test', $page->render());
- }
-
- public function testRenderAfterHook()
- {
- $app = new App([
- 'roots' => [
- 'index' => static::TMP
- ],
- 'templates' => [
- 'foo' => static::FIXTURES . '/PageRenderHookTest/foo.php'
- ],
- 'site' => [
- 'children' => [
- [
- 'slug' => 'foo',
- 'template' => 'foo',
- 'content' => [
- 'title' => 'Foo Title',
- ]
- ]
- ],
- ],
- 'hooks' => [
- 'page.render:after' => function ($contentType, $data, $html, $page) {
- return str_replace(':', '-', $html);
- }
- ]
- ]);
-
- $page = $app->page('foo');
- $this->assertSame('foo - Foo Title', $page->render());
- }
}
diff --git a/tests/Cms/Pages/fixtures/PageRenderTest/controllers/controller.php b/tests/Cms/Pages/fixtures/PageRenderTest/controllers/controller.php
new file mode 100644
index 0000000000..6580bf3e40
--- /dev/null
+++ b/tests/Cms/Pages/fixtures/PageRenderTest/controllers/controller.php
@@ -0,0 +1,7 @@
+ 'TEST: ' . $page->title()
+ ];
+};
diff --git a/tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-data.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-data.php
new file mode 100644
index 0000000000..d95089c9a1
--- /dev/null
+++ b/tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-data.php
@@ -0,0 +1 @@
+This is a = $test ?? 'test' ?>: = uniqid() ?>
diff --git a/tests/Cms/Pages/fixtures/PageCacheTest/default.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-default.php
similarity index 100%
rename from tests/Cms/Pages/fixtures/PageCacheTest/default.php
rename to tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-default.php
diff --git a/tests/Cms/Pages/fixtures/PageCacheTest/disabled.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-disabled.php
similarity index 100%
rename from tests/Cms/Pages/fixtures/PageCacheTest/disabled.php
rename to tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-disabled.php
diff --git a/tests/Cms/Pages/fixtures/PageCacheTest/dynamic.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-dynamic.php
similarity index 100%
rename from tests/Cms/Pages/fixtures/PageCacheTest/dynamic.php
rename to tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-dynamic.php
diff --git a/tests/Cms/Pages/fixtures/PageCacheTest/expiry.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-expiry.php
similarity index 100%
rename from tests/Cms/Pages/fixtures/PageCacheTest/expiry.php
rename to tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-expiry.php
diff --git a/tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-metadata.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-metadata.php
new file mode 100644
index 0000000000..b56a59d50f
--- /dev/null
+++ b/tests/Cms/Pages/fixtures/PageRenderTest/templates/cache-metadata.php
@@ -0,0 +1,7 @@
+response()->code(202);
+$kirby->response()->header('Cache-Control', 'private');
+$kirby->response()->type('text/plain');
+
+echo 'This is a test: ' . uniqid();
diff --git a/tests/Cms/Pages/fixtures/PageRenderTest/templates/controller.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/controller.php
new file mode 100644
index 0000000000..bc3c982ccb
--- /dev/null
+++ b/tests/Cms/Pages/fixtures/PageRenderTest/templates/controller.php
@@ -0,0 +1 @@
+Data says = $test ?> and = $test2 ?? 'default' ?>!
\ No newline at end of file
diff --git a/tests/Cms/Pages/fixtures/PageRenderHookTest/bar.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/hook-bar.php
similarity index 100%
rename from tests/Cms/Pages/fixtures/PageRenderHookTest/bar.php
rename to tests/Cms/Pages/fixtures/PageRenderTest/templates/hook-bar.php
diff --git a/tests/Cms/Pages/fixtures/PageRenderHookTest/foo.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/hook-foo.php
similarity index 100%
rename from tests/Cms/Pages/fixtures/PageRenderHookTest/foo.php
rename to tests/Cms/Pages/fixtures/PageRenderTest/templates/hook-foo.php
diff --git a/tests/Cms/Pages/fixtures/PageRenderTest/templates/representation.json.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/representation.json.php
new file mode 100644
index 0000000000..ae0a94c3f4
--- /dev/null
+++ b/tests/Cms/Pages/fixtures/PageRenderTest/templates/representation.json.php
@@ -0,0 +1 @@
+{"some json": "= $page->title() ?>"}
\ No newline at end of file
diff --git a/tests/Cms/Pages/fixtures/PageRenderTest/templates/representation.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/representation.php
new file mode 100644
index 0000000000..12abc0f002
--- /dev/null
+++ b/tests/Cms/Pages/fixtures/PageRenderTest/templates/representation.php
@@ -0,0 +1 @@
+Some HTML: = $page->title() ?>
\ No newline at end of file
diff --git a/tests/Cms/Pages/fixtures/PageRenderTest/templates/version-exception.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/version-exception.php
new file mode 100644
index 0000000000..f179ba1a03
--- /dev/null
+++ b/tests/Cms/Pages/fixtures/PageRenderTest/templates/version-exception.php
@@ -0,0 +1,13 @@
+value() !== 'changes') {
+ throw new AssertionFailedError('Version ID is not changes');
+}
+
+// this one is expected and used in the test
+throw new Exception('Something went wrong');
diff --git a/tests/Cms/Pages/fixtures/PageRenderTest/templates/version-recursive.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/version-recursive.php
new file mode 100644
index 0000000000..0d8219bcc7
--- /dev/null
+++ b/tests/Cms/Pages/fixtures/PageRenderTest/templates/version-recursive.php
@@ -0,0 +1,3 @@
+
+= page('version')->render() . "\n" ?>
+
\ No newline at end of file
diff --git a/tests/Cms/Pages/fixtures/PageRenderTest/templates/version.php b/tests/Cms/Pages/fixtures/PageRenderTest/templates/version.php
new file mode 100644
index 0000000000..a4c531cdb9
--- /dev/null
+++ b/tests/Cms/Pages/fixtures/PageRenderTest/templates/version.php
@@ -0,0 +1,2 @@
+Version: = (\Kirby\Content\VersionId::$render?->value() ?? 'none') . "\n"?>
+Content: = $page->title() ?>
diff --git a/tests/Cms/System/UpdateStatusTest.php b/tests/Cms/System/UpdateStatusTest.php
index bad04834dc..9899b9dc27 100644
--- a/tests/Cms/System/UpdateStatusTest.php
+++ b/tests/Cms/System/UpdateStatusTest.php
@@ -72,7 +72,7 @@ public function testLoadData()
'8.0' => '2023-11-26',
'8.1' => '2024-11-25',
'8.2' => '2025-12-08',
- '8.3' => '2026-11-23'
+ '8.3' => '2026-11-23',
],
'incidents' => [],
'messages' => [],
diff --git a/tests/Content/ChangesTest.php b/tests/Content/ChangesTest.php
index d195d445cf..f23364b3eb 100644
--- a/tests/Content/ChangesTest.php
+++ b/tests/Content/ChangesTest.php
@@ -2,8 +2,8 @@
namespace Kirby\Content;
+use Kirby\Cache\Cache;
use Kirby\Cms\App;
-use Kirby\Cms\Site;
use Kirby\TestCase;
use Kirby\Uuid\Uuids;
@@ -58,55 +58,138 @@ public function tearDown(): void
}
/**
- * @covers ::field
+ * @covers ::cache
*/
- public function testField()
+ public function testCache()
{
- $changes = new Changes();
- $this->assertInstanceOf(Field::class, $changes->field());
+ $cache = $this->app->cache('changes');
+
+ $this->assertInstanceOf(Cache::class, $cache);
}
/**
* @covers ::files
+ * @covers ::ensure
*/
public function testFiles()
{
- $site = App::instance()->site()->update([
- 'changes' => [
- 'file://test'
- ]
+ $this->app->cache('changes')->set('files', $cache = [
+ 'file://test'
]);
$changes = new Changes();
+ // in cache, but changes don't exist in reality
+ $this->assertCount(0, $changes->files());
+ $this->assertSame([], $this->app->cache('changes')->get('files'));
+
+ // in cache and changes exist in reality
+ $this->app->file('test/test.jpg')->version(VersionId::latest())->save([]);
+ $this->app->file('test/test.jpg')->version(VersionId::changes())->save([]);
+
+ $this->assertSame($cache, $this->app->cache('changes')->get('files'));
$this->assertCount(1, $changes->files());
$this->assertSame('test/test.jpg', $changes->files()->first()->id());
}
+ /**
+ * @covers ::cacheKey
+ */
+ public function testCacheKey()
+ {
+ $changes = new Changes();
+
+ $page = $this->app->page('test');
+ $file = $this->app->file('test/test.jpg');
+ $user = $this->app->user('test');
+
+ $this->assertSame('pages', $changes->cacheKey($page));
+ $this->assertSame('files', $changes->cacheKey($file));
+ $this->assertSame('users', $changes->cacheKey($user));
+ }
+
+ /**
+ * @covers ::cacheExists
+ * @covers ::generateCache
+ */
+ public function testGenerateCache()
+ {
+ $changes = new Changes();
+
+ $file = $this->app->file('test/test.jpg');
+ $file->version(VersionId::latest())->save(['foo' => 'bar']);
+ $file->version(VersionId::changes())->save(['foo' => 'bar']);
+
+ $page = $this->app->page('test');
+ $page->version(VersionId::latest())->save(['foo' => 'bar']);
+ $page->version(VersionId::changes())->save(['foo' => 'bar']);
+
+ $user = $this->app->user('test');
+ $user->version(VersionId::latest())->save(['foo' => 'bar']);
+ $user->version(VersionId::changes())->save(['foo' => 'bar']);
+
+ $this->app->cache('changes')->flush();
+
+ $this->assertFalse($changes->cacheExists());
+ $this->assertSame([], $changes->read('files'));
+ $this->assertSame([], $changes->read('pages'));
+ $this->assertSame([], $changes->read('users'));
+
+ $changes->generateCache();
+
+ $this->assertTrue($changes->cacheExists());
+ $this->assertSame(['file://test'], $changes->read('files'));
+ $this->assertSame(['page://test'], $changes->read('pages'));
+ $this->assertSame(['user://test'], $changes->read('users'));
+ }
+
/**
* @covers ::pages
+ * @covers ::ensure
*/
public function testPages()
{
- $site = App::instance()->site()->update([
- 'changes' => [
- 'page://test'
- ]
+ $this->app->cache('changes')->set('pages', $cache = [
+ 'page://test'
]);
$changes = new Changes();
+ // in cache, but changes don't exist in reality
+ $this->assertCount(0, $changes->pages());
+ $this->assertSame([], $this->app->cache('changes')->get('pages'));
+
+ // in cache and changes exist in reality
+ $this->app->page('test')->version(VersionId::latest())->save([]);
+ $this->app->page('test')->version(VersionId::changes())->save([]);
+
+ $this->assertSame($cache, $this->app->cache('changes')->get('pages'));
$this->assertCount(1, $changes->pages());
$this->assertSame('test', $changes->pages()->first()->id());
}
/**
- * @covers ::site
+ * @covers ::read
*/
- public function testSite()
+ public function testRead()
{
+ $this->app->cache('changes')->set('files', [
+ 'file://test'
+ ]);
+
+ $this->app->cache('changes')->set('pages', [
+ 'page://test'
+ ]);
+
+ $this->app->cache('changes')->set('users', [
+ 'user://test'
+ ]);
+
$changes = new Changes();
- $this->assertInstanceOf(Site::class, $changes->site());
+
+ $this->assertSame(['file://test'], $changes->read('files'));
+ $this->assertSame(['page://test'], $changes->read('pages'));
+ $this->assertSame(['user://test'], $changes->read('users'));
}
/**
@@ -116,21 +199,53 @@ public function testTrack()
{
$changes = new Changes();
- $this->assertCount(0, $changes->files());
- $this->assertCount(0, $changes->pages());
- $this->assertCount(0, $changes->users());
+ $this->assertCount(0, $changes->read('files'));
+ $this->assertCount(0, $changes->read('pages'));
+ $this->assertCount(0, $changes->read('users'));
$changes->track($this->app->page('test'));
$changes->track($this->app->file('test/test.jpg'));
$changes->track($this->app->user('test'));
- $this->assertCount(1, $changes->files());
- $this->assertCount(1, $changes->pages());
- $this->assertCount(1, $changes->users());
+ $this->assertCount(1, $files = $changes->read('files'));
+ $this->assertCount(1, $pages = $changes->read('pages'));
+ $this->assertCount(1, $users = $changes->read('users'));
- $this->assertSame('test', $changes->pages()->first()->id());
- $this->assertSame('test/test.jpg', $changes->files()->first()->id());
- $this->assertSame('test', $changes->users()->first()->id());
+ $this->assertSame('file://test', $files[0]);
+ $this->assertSame('page://test', $pages[0]);
+ $this->assertSame('user://test', $users[0]);
+ }
+
+ /**
+ * @covers ::track
+ */
+ public function testTrackDisabledUuids()
+ {
+ $this->app = $this->app->clone([
+ 'options' => [
+ 'content' => [
+ 'uuid' => false
+ ]
+ ]
+ ]);
+
+ $changes = new Changes();
+
+ $this->assertCount(0, $changes->read('files'));
+ $this->assertCount(0, $changes->read('pages'));
+ $this->assertCount(0, $changes->read('users'));
+
+ $changes->track($this->app->page('test'));
+ $changes->track($this->app->file('test/test.jpg'));
+ $changes->track($this->app->user('test'));
+
+ $this->assertCount(1, $files = $changes->read('files'));
+ $this->assertCount(1, $pages = $changes->read('pages'));
+ $this->assertCount(1, $users = $changes->read('users'));
+
+ $this->assertSame('test/test.jpg', $files[0]);
+ $this->assertSame('test', $pages[0]);
+ $this->assertSame('test', $users[0]);
}
/**
@@ -140,21 +255,29 @@ public function testUpdate()
{
$changes = new Changes();
- $changes->update([
+ $changes->update('files', [
+ $this->app->file('test/test.jpg')->uuid()->toString(),
+ ]);
+
+ $changes->update('pages', [
$this->app->page('test')->uuid()->toString(),
- $this->app->file('test/test.jpg')->toString(),
- $this->app->user('test')->toString()
]);
- $this->assertCount(1, $changes->files());
- $this->assertCount(1, $changes->pages());
- $this->assertCount(1, $changes->users());
+ $changes->update('users', [
+ $this->app->user('test')->uuid()->toString()
+ ]);
- $changes->update([]);
+ $this->assertCount(1, $changes->read('files'));
+ $this->assertCount(1, $changes->read('pages'));
+ $this->assertCount(1, $changes->read('users'));
- $this->assertCount(0, $changes->files());
- $this->assertCount(0, $changes->pages());
- $this->assertCount(0, $changes->users());
+ $changes->update('files', []);
+ $changes->update('pages', []);
+ $changes->update('users', []);
+
+ $this->assertCount(0, $changes->read('files'));
+ $this->assertCount(0, $changes->read('pages'));
+ $this->assertCount(0, $changes->read('users'));
}
/**
@@ -168,32 +291,72 @@ public function testUntrack()
$changes->track($this->app->file('test/test.jpg'));
$changes->track($this->app->user('test'));
- $this->assertCount(1, $changes->files());
- $this->assertCount(1, $changes->pages());
- $this->assertCount(1, $changes->users());
+ $this->assertCount(1, $changes->read('files'));
+ $this->assertCount(1, $changes->read('pages'));
+ $this->assertCount(1, $changes->read('users'));
$changes->untrack($this->app->page('test'));
$changes->untrack($this->app->file('test/test.jpg'));
$changes->untrack($this->app->user('test'));
- $this->assertCount(0, $changes->files());
- $this->assertCount(0, $changes->pages());
- $this->assertCount(0, $changes->users());
+ $this->assertCount(0, $changes->read('files'));
+ $this->assertCount(0, $changes->read('pages'));
+ $this->assertCount(0, $changes->read('users'));
+ }
+
+ /**
+ * @covers ::untrack
+ */
+ public function testUntrackDisabledUuids()
+ {
+ $this->app = $this->app->clone([
+ 'options' => [
+ 'content' => [
+ 'uuid' => false
+ ]
+ ]
+ ]);
+
+ $changes = new Changes();
+
+ $changes->track($this->app->page('test'));
+ $changes->track($this->app->file('test/test.jpg'));
+ $changes->track($this->app->user('test'));
+
+ $this->assertCount(1, $changes->read('files'));
+ $this->assertCount(1, $changes->read('pages'));
+ $this->assertCount(1, $changes->read('users'));
+
+ $changes->untrack($this->app->page('test'));
+ $changes->untrack($this->app->file('test/test.jpg'));
+ $changes->untrack($this->app->user('test'));
+
+ $this->assertCount(0, $changes->read('files'));
+ $this->assertCount(0, $changes->read('pages'));
+ $this->assertCount(0, $changes->read('users'));
}
/**
* @covers ::users
+ * @covers ::ensure
*/
public function testUsers()
{
- $site = App::instance()->site()->update([
- 'changes' => [
- 'user://test'
- ]
+ $this->app->cache('changes')->set('users', $cache = [
+ 'user://test'
]);
$changes = new Changes();
+ // in cache, but changes don't exist in reality
+ $this->assertCount(0, $changes->users());
+ $this->assertSame([], $this->app->cache('changes')->get('users'));
+
+ // in cache and changes exist in reality
+ $this->app->user('test')->version(VersionId::latest())->save([]);
+ $this->app->user('test')->version(VersionId::changes())->save([]);
+
+ $this->assertSame($cache, $this->app->cache('changes')->get('users'));
$this->assertCount(1, $changes->users());
$this->assertSame('test', $changes->users()->first()->id());
}
diff --git a/tests/Content/FieldMethodsTest.php b/tests/Content/FieldMethodsTest.php
index 1819118396..1c303c891b 100644
--- a/tests/Content/FieldMethodsTest.php
+++ b/tests/Content/FieldMethodsTest.php
@@ -20,7 +20,7 @@
class FieldMethodsTest extends TestCase
{
public const FIXTURES = __DIR__ . '/fixtures';
- public const TMP = KIRBY_TMP_DIR . '/Filesystem.FieldMethods';
+ public const TMP = KIRBY_TMP_DIR . '/Content.FieldMethods';
public function setUp(): void
{
diff --git a/tests/Content/ImmutableMemoryContentStorageHandlerTest.php b/tests/Content/ImmutableMemoryStorageTest.php
similarity index 89%
rename from tests/Content/ImmutableMemoryContentStorageHandlerTest.php
rename to tests/Content/ImmutableMemoryStorageTest.php
index 6634d4abd8..f4b602fc7a 100644
--- a/tests/Content/ImmutableMemoryContentStorageHandlerTest.php
+++ b/tests/Content/ImmutableMemoryStorageTest.php
@@ -6,10 +6,10 @@
use Kirby\Exception\LogicException;
/**
- * @coversDefaultClass \Kirby\Content\ImmutableMemoryContentStorageHandler
+ * @coversDefaultClass \Kirby\Content\ImmutableMemoryStorage
* @covers ::__construct
*/
-class ImmutableMemoryContentStorageHandlerTest extends TestCase
+class ImmutableMemoryStorageTest extends TestCase
{
protected $storage;
@@ -18,7 +18,7 @@ public function setUp(): void
parent::setUp();
parent::setUpSingleLanguage();
- $this->storage = new ImmutableMemoryContentStorageHandler($this->model);
+ $this->storage = new ImmutableMemoryStorage($this->model);
}
/**
diff --git a/tests/Content/LockTest.php b/tests/Content/LockTest.php
new file mode 100644
index 0000000000..d4871a87f4
--- /dev/null
+++ b/tests/Content/LockTest.php
@@ -0,0 +1,483 @@
+app->page('test'),
+ id: VersionId::changes()
+ );
+
+ $version->create([
+ 'title' => 'Test'
+ ], $language);
+
+ return $version;
+ }
+
+ protected function createLatestVersion(
+ Language|string $language = 'default'
+ ): Version {
+ $latest = new Version(
+ model: $this->app->page('test'),
+ id: VersionId::latest()
+ );
+
+ $latest->create([
+ 'title' => 'Test'
+ ], $language);
+
+ return $latest;
+ }
+
+ public function setUp(): void
+ {
+ $this->app = new App([
+ 'roots' => [
+ 'index' => static::TMP
+ ],
+ 'site' => [
+ 'children' => [
+ [
+ 'slug' => 'test'
+ ]
+ ]
+ ],
+ 'users' => [
+ [
+ 'email' => 'admin@getkirby.com',
+ 'id' => 'admin',
+ ],
+ [
+ 'email' => 'editor@getkirby.com',
+ 'id' => 'editor',
+ ]
+ ]
+ ]);
+ }
+
+ /**
+ * @covers ::for
+ */
+ public function testForWithAuthenticatedUser()
+ {
+ $this->app->impersonate('admin');
+
+ $latest = $this->createLatestVersion();
+ $changes = $this->createChangesVersion();
+ $lock = Lock::for($changes);
+
+ $this->assertTrue($lock->isActive());
+ $this->assertFalse($lock->isLocked());
+ $this->assertSame($this->app->user('admin'), $lock->user());
+ }
+
+ /**
+ * @covers ::for
+ */
+ public function testForWithDifferentUser()
+ {
+ // create the version with the admin user
+ $this->app->impersonate('admin');
+
+ $latest = $this->createLatestVersion();
+ $changes = $this->createChangesVersion();
+
+ // switch to a different user to simulate locked content
+ $this->app->impersonate('editor');
+
+ $lock = Lock::for($changes);
+
+ $this->assertTrue($lock->isActive());
+ $this->assertTrue($lock->isLocked());
+ $this->assertSame($this->app->user('admin'), $lock->user());
+ }
+
+ /**
+ * @covers ::for
+ */
+ public function testForWithoutUser()
+ {
+ // create the version with the admin user
+ $this->app->impersonate('admin');
+
+ $latest = $this->createLatestVersion();
+ $lock = Lock::for($latest);
+
+ $this->assertNull($lock->user());
+ }
+
+ /**
+ * @covers ::for
+ */
+ public function testForWithLanguageWildcard()
+ {
+ $this->app = $this->app->clone([
+ 'languages' => [
+ [
+ 'code' => 'en',
+ 'default' => true
+ ],
+ [
+ 'code' => 'de'
+ ]
+ ]
+ ]);
+
+ // create the version with the admin user
+ $this->app->impersonate('admin');
+
+ $this->createLatestVersion('en');
+ $this->createLatestVersion('de');
+
+ $this->createChangesVersion('de');
+
+ // switch to a different user to simulate locked content
+ $this->app->impersonate('editor');
+
+ $changes = $this->app->page('test')->version('changes');
+ $lock = Lock::for($changes, '*');
+
+ $this->assertSame('admin', $lock->user()->id());
+ }
+
+ /**
+ * @covers ::for
+ */
+ public function testForWithLegacyLock()
+ {
+ $page = $this->app->page('test');
+ $file = $page->root() . '/.lock';
+
+ Data::write($file, [
+ '/' . $page->id() => [
+ 'lock' => [
+ 'user' => 'editor',
+ 'time' => $time = time()
+ ]
+ ]
+ ], 'yml');
+
+ $lock = Lock::for($page->version('changes'));
+ $this->assertInstanceOf(Lock::class, $lock);
+ $this->assertTrue($lock->isLocked());
+ }
+
+ /**
+ * @covers ::isActive
+ */
+ public function testIsActive()
+ {
+ // just modified
+ $lock = new Lock(
+ modified: time()
+ );
+
+ $this->assertTrue($lock->isActive());
+ }
+
+ /**
+ * @covers ::isActive
+ */
+ public function testIsActiveWithOldModificationTimestamp()
+ {
+ // create a lock that has not been modified for 20 minutes
+ $lock = new Lock(
+ modified: time() - 60 * 20
+ );
+
+ $this->assertFalse($lock->isActive());
+ }
+
+ /**
+ * @covers ::isActive
+ */
+ public function testIsActiveWithoutModificationTimestamp()
+ {
+ // a lock without modification time should also be inactive
+ $lock = new Lock();
+ $this->assertFalse($lock->isActive());
+ }
+
+ /**
+ * @covers ::isEnabled
+ */
+ public function testIsEnabled()
+ {
+ $this->assertTrue(Lock::isEnabled());
+ }
+
+ /**
+ * @covers ::isEnabled
+ */
+ public function testIsEnabledWhenDisabled()
+ {
+ $this->app = $this->app->clone([
+ 'options' => [
+ 'content' => [
+ 'locking' => false,
+ ]
+ ]
+ ]);
+
+ $this->assertFalse(Lock::isEnabled());
+ }
+
+ /**
+ * @covers ::isLegacy
+ */
+ public function testIsLegacy()
+ {
+ $lock = new Lock();
+ $this->assertFalse($lock->isLegacy());
+
+ $lock = new Lock(legacy: true);
+ $this->assertTrue($lock->isLegacy());
+ }
+
+ /**
+ * @covers ::isLocked
+ */
+ public function testIsLocked()
+ {
+ $lock = new Lock();
+ $this->assertFalse($lock->isLocked());
+ }
+
+ /**
+ * @covers ::isLocked
+ */
+ public function testIsLockedWithCurrentUser()
+ {
+ $this->app->impersonate('admin');
+
+ $lock = new Lock(
+ modified: time(),
+ user: $this->app->user('admin')
+ );
+
+ $this->assertFalse($lock->isLocked());
+ }
+
+ /**
+ * @covers ::isLocked
+ */
+ public function testIsLockedWithDifferentUser()
+ {
+ $this->app->impersonate('admin');
+
+ $lock = new Lock(
+ modified: time(),
+ user: $this->app->user('editor')
+ );
+
+ $this->assertTrue($lock->isLocked());
+ }
+
+ /**
+ * @covers ::isLocked
+ */
+ public function testIsLockedWhenDisabled()
+ {
+ $this->app = $this->app->clone([
+ 'options' => [
+ 'content' => [
+ 'locking' => false
+ ]
+ ]
+ ]);
+
+ $this->app->impersonate('admin');
+
+ $lock = new Lock(
+ modified: time(),
+ user: $this->app->user('editor')
+ );
+
+ $this->assertFalse($lock->isLocked());
+ }
+
+ /**
+ * @covers ::isLocked
+ */
+ public function testIsLockedWithDifferentUserAndOldTimestamp()
+ {
+ $this->app->impersonate('admin');
+
+ $lock = new Lock(
+ modified: time() - 60 * 20,
+ user: $this->app->user('editor')
+ );
+
+ $this->assertFalse($lock->isLocked());
+ }
+
+ /**
+ * @covers ::legacy
+ */
+ public function testLegacy()
+ {
+ $page = $this->app->page('test');
+ $file = $page->root() . '/.lock';
+
+ Data::write($file, [
+ '/' . $page->id() => [
+ 'lock' => [
+ 'user' => 'editor',
+ 'time' => $time = time()
+ ]
+ ]
+ ], 'yml');
+
+ $lock = Lock::legacy($page);
+
+ $this->assertInstanceOf(Lock::class, $lock);
+ $this->assertTrue($lock->isLocked());
+ $this->assertTrue($lock->isLegacy());
+ $this->assertSame($this->app->user('editor'), $lock->user());
+ $this->assertSame($time, $lock->modified());
+ }
+
+ /**
+ * @covers ::legacy
+ */
+ public function testLegacyWithoutLockInfo()
+ {
+ $page = $this->app->page('test');
+ $file = $page->root() . '/.lock';
+
+ Data::write($file, [], 'yml');
+
+ $lock = Lock::legacy($page);
+ $this->assertNull($lock);
+ }
+
+ /**
+ * @covers ::legacy
+ */
+ public function testLegacyWithOutdatedFile()
+ {
+ $page = $this->app->page('test');
+ $file = $page->root() . '/.lock';
+
+ Data::write($file, [
+ '/' . $page->id() => [
+ 'lock' => [
+ 'user' => 'editor',
+ 'time' => time() - 60 * 60 * 24
+ ],
+ ]
+ ], 'yml');
+
+ $lock = Lock::legacy($page);
+
+ $this->assertInstanceOf(Lock::class, $lock);
+ $this->assertFalse($lock->isLocked());
+ }
+
+ /**
+ * @covers ::legacy
+ */
+ public function testLegacyWithUnlockedFile()
+ {
+ $page = $this->app->page('test');
+ $file = $page->root() . '/.lock';
+
+ Data::write($file, [
+ '/' . $page->id() => [
+ 'lock' => [
+ 'user' => 'editor',
+ 'time' => time()
+ ],
+ 'unlock' => ['admin']
+ ]
+ ], 'yml');
+
+ $lock = Lock::legacy($page);
+ $this->assertNull($lock);
+ }
+
+ /**
+ * @covers ::legacyFile
+ */
+ public function testLegacyFile()
+ {
+ $page = $this->app->page('test');
+ $expected = $page->root() . '/.lock';
+
+ $this->assertSame($expected, Lock::legacyFile($page));
+ }
+
+ /**
+ * @covers ::modified
+ */
+ public function testModified()
+ {
+ $lock = new Lock(
+ modified: $modified = time()
+ );
+
+ $this->assertSame($modified, $lock->modified());
+ $this->assertSame(date('c', $modified), $lock->modified('c'));
+ }
+
+ /**
+ * @covers ::toArray
+ */
+ public function testToArray()
+ {
+ $lock = new Lock(
+ user: $user = new User([
+ 'email' => 'test@getkirby.com',
+ 'id' => 'test'
+ ]),
+ modified: $modified = time()
+ );
+
+ $this->assertSame([
+ 'isLegacy' => false,
+ 'isLocked' => true,
+ 'modified' => date('c', $modified),
+ 'user' => [
+ 'id' => 'test',
+ 'email' => 'test@getkirby.com'
+ ]
+ ], $lock->toArray());
+ }
+
+ /**
+ * @covers ::user
+ */
+ public function testUser()
+ {
+ $lock = new Lock(
+ user: $user = $this->app->user('admin')
+ );
+
+ $this->assertSame($user, $lock->user());
+ }
+
+ /**
+ * @covers ::user
+ */
+ public function testUserWithoutUser()
+ {
+ $lock = new Lock();
+ $this->assertNull($lock->user());
+ }
+}
diff --git a/tests/Content/LockedContentExceptionTest.php b/tests/Content/LockedContentExceptionTest.php
new file mode 100644
index 0000000000..b987f34b6e
--- /dev/null
+++ b/tests/Content/LockedContentExceptionTest.php
@@ -0,0 +1,43 @@
+ 'test']),
+ modified: $time = time()
+ );
+
+ $exception = new LockedContentException(
+ lock: $lock
+ );
+
+ $this->assertSame('The version is locked', $exception->getMessage());
+ $this->assertSame($lock->toArray(), $exception->getDetails());
+ $this->assertSame(423, $exception->getHttpCode());
+ $this->assertSame('error.content.lock', $exception->getKey());
+ }
+
+ public function testCustomMessage()
+ {
+ $lock = new Lock(
+ user: new User(['username' => 'test']),
+ modified: $time = time()
+ );
+
+ $exception = new LockedContentException(
+ lock: $lock,
+ message: $message = 'The version is locked and cannot be deleted'
+ );
+
+ $this->assertSame($message, $exception->getMessage());
+ }
+}
diff --git a/tests/Content/MemoryContentStorageHandlerTest.php b/tests/Content/MemoryStorageTest.php
similarity index 90%
rename from tests/Content/MemoryContentStorageHandlerTest.php
rename to tests/Content/MemoryStorageTest.php
index c36fd0eca0..c24f87af46 100644
--- a/tests/Content/MemoryContentStorageHandlerTest.php
+++ b/tests/Content/MemoryStorageTest.php
@@ -3,16 +3,15 @@
namespace Kirby\Content;
use Kirby\Cms\Language;
-use Kirby\Exception\NotFoundException;
/**
- * @coversDefaultClass Kirby\Content\MemoryContentStorageHandler
+ * @coversDefaultClass Kirby\Content\MemoryStorage
* @covers ::__construct
* @covers ::cacheId
*/
-class MemoryContentStorageHandlerTest extends TestCase
+class MemoryStorageTest extends TestCase
{
- protected MemoryContentStorageHandler $storage;
+ protected MemoryStorage $storage;
public function assertCreateAndDelete(VersionId $versionId, Language $language): void
{
@@ -60,7 +59,7 @@ public function setUpMultiLanguage(
): void {
parent::setUpMultiLanguage(site: $site);
- $this->storage = new MemoryContentStorageHandler($this->model);
+ $this->storage = new MemoryStorage($this->model);
}
public function setUpSingleLanguage(
@@ -68,7 +67,7 @@ public function setUpSingleLanguage(
): void {
parent::setUpSingleLanguage(site: $site);
- $this->storage = new MemoryContentStorageHandler($this->model);
+ $this->storage = new MemoryStorage($this->model);
}
/**
@@ -106,7 +105,7 @@ public function testCreateAndReadChangesSingleLang()
* @covers ::read
* @covers ::write
*/
- public function testCreateAndReadPublishedMultiLang()
+ public function testCreateAndReadLatestMultiLang()
{
$this->setUpMultiLanguage();
@@ -121,7 +120,7 @@ public function testCreateAndReadPublishedMultiLang()
* @covers ::read
* @covers ::write
*/
- public function testCreateAndReadPublishedSingleLang()
+ public function testCreateAndReadLatestSingleLang()
{
$this->setUpSingleLanguage();
@@ -179,7 +178,7 @@ public function testDeleteChangesSingleLang()
/**
* @covers ::delete
*/
- public function testDeletePublishedMultiLang()
+ public function testDeleteLatestMultiLang()
{
$this->setUpMultiLanguage();
@@ -192,7 +191,7 @@ public function testDeletePublishedMultiLang()
/**
* @covers ::delete
*/
- public function testDeletePublishedSingleLang()
+ public function testDeleteLatestSingleLang()
{
$this->setUpSingleLanguage();
@@ -313,19 +312,6 @@ public function testModifiedSomeExistingSingleLanguage()
$this->assertNull($this->storage->modified(VersionId::latest(), $language));
}
- /**
- * @covers ::read
- */
- public function testReadWhenMissing()
- {
- $this->setUpSingleLanguage();
-
- $this->expectException(NotFoundException::class);
- $this->expectExceptionMessage('Version "changes" does not already exist');
-
- $this->storage->read(VersionId::changes(), Language::single());
- }
-
/**
* @covers ::touch
*/
diff --git a/tests/Content/PlainTextContentStorageHandlerTest.php b/tests/Content/PlainTextStorageTest.php
similarity index 91%
rename from tests/Content/PlainTextContentStorageHandlerTest.php
rename to tests/Content/PlainTextStorageTest.php
index a2bcce8f3a..a6702cd0d8 100644
--- a/tests/Content/PlainTextContentStorageHandlerTest.php
+++ b/tests/Content/PlainTextStorageTest.php
@@ -7,16 +7,15 @@
use Kirby\Cms\Page;
use Kirby\Cms\User;
use Kirby\Data\Data;
-use Kirby\Exception\NotFoundException;
use Kirby\Filesystem\Dir;
/**
- * @coversDefaultClass Kirby\Content\PlainTextContentStorageHandler
+ * @coversDefaultClass Kirby\Content\PlainTextStorage
* @covers ::__construct
*/
-class PlainTextContentStorageHandlerTest extends TestCase
+class PlainTextStorageTest extends TestCase
{
- public const TMP = KIRBY_TMP_DIR . '/Content.PlainTextContentStorage';
+ public const TMP = KIRBY_TMP_DIR . '/Content.PlainTextStorage';
protected $storage;
@@ -25,7 +24,7 @@ public function setUpMultiLanguage(
): void {
parent::setUpMultiLanguage(site: $site);
- $this->storage = new PlainTextContentStorageHandler($this->model);
+ $this->storage = new PlainTextStorage($this->model);
}
public function setUpSingleLanguage(
@@ -33,7 +32,7 @@ public function setUpSingleLanguage(
): void {
parent::setUpSingleLanguage(site: $site);
- $this->storage = new PlainTextContentStorageHandler($this->model);
+ $this->storage = new PlainTextStorage($this->model);
}
/**
@@ -71,7 +70,7 @@ public function testCreateChangesSingleLang()
/**
* @covers ::create
*/
- public function testCreatePublishedMultiLang()
+ public function testCreateLatestMultiLang()
{
$this->setUpMultiLanguage();
@@ -87,7 +86,7 @@ public function testCreatePublishedMultiLang()
/**
* @covers ::create
*/
- public function testCreatePublishedSingleLang()
+ public function testCreateLatestSingleLang()
{
$this->setUpSingleLanguage();
@@ -157,7 +156,7 @@ public function testDeleteChangesSingleLang()
/**
* @covers ::delete
*/
- public function testDeletePublishedMultiLang()
+ public function testDeleteLatestMultiLang()
{
$this->setUpMultiLanguage();
@@ -173,7 +172,7 @@ public function testDeletePublishedMultiLang()
/**
* @covers ::delete
*/
- public function testDeletePublishedSingleLang()
+ public function testDeleteLatestSingleLang()
{
$this->setUpSingleLanguage();
@@ -347,7 +346,7 @@ public function testReadChangesSingleLang()
/**
* @covers ::read
*/
- public function testReadPublishedMultiLang()
+ public function testReadLatestMultiLang()
{
$this->setUpMultiLanguage();
@@ -364,7 +363,7 @@ public function testReadPublishedMultiLang()
/**
* @covers ::read
*/
- public function testReadPublishedSingleLang()
+ public function testReadLatestSingleLang()
{
$this->setUpSingleLanguage();
@@ -378,19 +377,6 @@ public function testReadPublishedSingleLang()
$this->assertSame($fields, $this->storage->read(VersionId::latest(), Language::single()));
}
- /**
- * @covers ::read
- */
- public function testReadWhenMissing()
- {
- $this->setUpSingleLanguage();
-
- $this->expectException(NotFoundException::class);
- $this->expectExceptionMessage('Version "changes" does not already exist');
-
- $this->storage->read(VersionId::changes(), Language::single());
- }
-
/**
* @covers ::touch
*/
@@ -436,7 +422,7 @@ public function testTouchChangesSingleLang()
/**
* @covers ::touch
*/
- public function testTouchPublishedMultiLang()
+ public function testTouchLatestMultiLang()
{
$this->setUpMultiLanguage();
@@ -456,7 +442,7 @@ public function testTouchPublishedMultiLang()
/**
* @covers ::touch
*/
- public function testTouchPublishedSingleLang()
+ public function testTouchLatestSingleLang()
{
$this->setUpSingleLanguage();
@@ -514,7 +500,7 @@ public function testUpdateChangesSingleLang()
/**
* @covers ::update
*/
- public function testUpdatePublishedMultiLang()
+ public function testUpdateLatestMultiLang()
{
$this->setUpMultiLanguage();
@@ -532,7 +518,7 @@ public function testUpdatePublishedMultiLang()
/**
* @covers ::update
*/
- public function testUpdatePublishedSingleLang()
+ public function testUpdateLatestSingleLang()
{
$this->setUpSingleLanguage();
@@ -574,7 +560,7 @@ public function testContentFileMultiLang(string $type, VersionId $id, string $la
])
};
- $storage = new PlainTextContentStorageHandler($model);
+ $storage = new PlainTextStorage($model);
$this->assertSame(static::TMP . '/' . $expected, $storage->contentFile($id, $this->app->language($language)));
}
@@ -619,7 +605,7 @@ public function testContentFileSingleLang(string $type, VersionId $id, string $e
])
};
- $storage = new PlainTextContentStorageHandler($model);
+ $storage = new PlainTextStorage($model);
$this->assertSame(static::TMP . '/' . $expected, $storage->contentFile($id, Language::single()));
}
@@ -651,7 +637,7 @@ public function testContentFileDraft()
'template' => 'article'
]);
- $storage = new PlainTextContentStorageHandler($model);
+ $storage = new PlainTextStorage($model);
$this->assertSame(static::TMP . '/content/_drafts/a-page/_changes/article.txt', $storage->contentFile(VersionId::changes(), Language::single()));
}
@@ -683,7 +669,7 @@ public function testContentFilesChangesSingleLang()
/**
* @covers ::contentFiles
*/
- public function testContentFilesPublishedMultiLang()
+ public function testContentFilesLatestMultiLang()
{
$this->setUpMultiLanguage();
@@ -696,7 +682,7 @@ public function testContentFilesPublishedMultiLang()
/**
* @covers ::contentFiles
*/
- public function testContentFilesPublishedSingleLang()
+ public function testContentFilesLatestSingleLang()
{
$this->setUpSingleLanguage();
diff --git a/tests/Content/ContentStorageHandlerTest.php b/tests/Content/StorageTest.php
similarity index 81%
rename from tests/Content/ContentStorageHandlerTest.php
rename to tests/Content/StorageTest.php
index 603d7e94ee..c272bef21f 100644
--- a/tests/Content/ContentStorageHandlerTest.php
+++ b/tests/Content/StorageTest.php
@@ -9,12 +9,12 @@
use Kirby\Filesystem\Dir;
/**
- * @coversDefaultClass Kirby\Content\ContentStorageHandler
+ * @coversDefaultClass Kirby\Content\Storage
* @covers ::__construct
*/
-class ContentStorageHandlerTest extends TestCase
+class StorageTest extends TestCase
{
- public const TMP = KIRBY_TMP_DIR . '/Content.ContentStorageHandler';
+ public const TMP = KIRBY_TMP_DIR . '/Content.Storage';
/**
* @covers ::all
@@ -23,7 +23,7 @@ public function testAllMultiLanguageForFile()
{
$this->setUpMultiLanguage();
- $handler = new TestContentStorageHandler(
+ $handler = new TestStorage(
new File([
'filename' => 'test.jpg',
'parent' => new Page(['slug' => 'test'])
@@ -58,7 +58,7 @@ public function testAllSingleLanguageForFile()
{
$this->setUpSingleLanguage();
- $handler = new TestContentStorageHandler(
+ $handler = new TestStorage(
new File([
'filename' => 'test.jpg',
'parent' => new Page(['slug' => 'test'])
@@ -88,7 +88,7 @@ public function testAllMultiLanguageForPage()
{
$this->setUpMultiLanguage();
- $handler = new TestContentStorageHandler(
+ $handler = new TestStorage(
new Page(['slug' => 'test', 'isDraft' => false])
);
@@ -106,8 +106,8 @@ public function testAllMultiLanguageForPage()
// count again
$versions = iterator_to_array($handler->all(), false);
- // A page that's not in draft mode can have latest and changes versions
- // and thus should have changes and latest for every language
+ // A page that's not in draft mode can have Latest and changes versions
+ // and thus should have changes and Latest for every language
$this->assertCount(4, $versions);
}
@@ -118,7 +118,7 @@ public function testAllSingleLanguageForPage()
{
$this->setUpSingleLanguage();
- $handler = new TestContentStorageHandler(
+ $handler = new TestStorage(
new Page(['slug' => 'test', 'isDraft' => false])
);
@@ -133,7 +133,7 @@ public function testAllSingleLanguageForPage()
// count again
$versions = iterator_to_array($handler->all(), false);
- // A page that's not in draft mode can have latest and changes versions
+ // A page that's not in draft mode can have Latest and changes versions
$this->assertCount(2, $versions);
}
@@ -144,9 +144,7 @@ public function testCopyMultiLanguage()
{
$this->setUpMultiLanguage();
- $handler = new TestContentStorageHandler(
- model: $this->model
- );
+ $handler = new TestStorage(model: $this->model);
$en = $this->app->language('en');
$de = $this->app->language('de');
@@ -173,9 +171,7 @@ public function testCopySingleLanguage()
{
$this->setUpSingleLanguage();
- $handler = new TestContentStorageHandler(
- model: $this->model
- );
+ $handler = new TestStorage(model: $this->model);
$handler->create(VersionId::latest(), Language::single(), []);
@@ -199,13 +195,8 @@ public function testCopytoAnotherStorage()
{
$this->setUpSingleLanguage();
- $handler1 = new TestContentStorageHandler(
- model: $this->model
- );
-
- $handler2 = new TestContentStorageHandler(
- model: $this->model
- );
+ $handler1 = new TestStorage(model: $this->model);
+ $handler2 = new TestStorage(model: $this->model);
$handler1->create(VersionId::latest(), Language::single(), []);
@@ -229,13 +220,8 @@ public function testCopyAll()
{
$this->setUpSingleLanguage();
- $handler1 = new TestContentStorageHandler(
- model: $this->model
- );
-
- $handler2 = new TestContentStorageHandler(
- model: $this->model
- );
+ $handler1 = new TestStorage(model: $this->model);
+ $handler2 = new TestStorage(model: $this->model);
$handler1->create(VersionId::latest(), Language::single(), []);
$handler1->create(VersionId::changes(), Language::single(), []);
@@ -260,9 +246,7 @@ public function testDeleteLanguageMultiLanguage()
{
$this->setUpMultiLanguage();
- $handler = new TestContentStorageHandler(
- model: $this->model
- );
+ $handler = new TestStorage(model: $this->model);
// create two versions for the German language
$handler->create(VersionId::latest(), $this->app->language('de'), []);
@@ -286,9 +270,7 @@ public function testDeleteLanguageSingleLanguage()
// Use the plain text handler, as the abstract class and the test handler do not
// implement the necessary methods to test this.
- $handler = new TestContentStorageHandler(
- model: $this->model
- );
+ $handler = new TestStorage(model: $this->model);
$language = Language::single();
@@ -312,23 +294,21 @@ public function testFrom()
{
$this->setUpMultiLanguage();
- $handlerA = new PlainTextContentStorageHandler(
- model: $this->model
- );
+ $handlerA = new PlainTextStorage(model: $this->model);
- $versionPublished = VersionId::latest();
- $versionChanges = VersionId::changes();
+ $versionLatest = VersionId::latest();
+ $versionChanges = VersionId::changes();
$en = $this->app->language('en');
$de = $this->app->language('de');
// create all possible versions
- $handlerA->create($versionPublished, $en, $latestEN = [
- 'title' => 'Published EN'
+ $handlerA->create($versionLatest, $en, $LatestEN = [
+ 'title' => 'Latest EN'
]);
- $handlerA->create($versionPublished, $de, $latestDE = [
- 'title' => 'Published DE'
+ $handlerA->create($versionLatest, $de, $LatestDE = [
+ 'title' => 'Latest DE'
]);
$handlerA->create($versionChanges, $en, $changesEN = [
@@ -340,12 +320,12 @@ public function testFrom()
]);
// create a new handler with all the versions from the first one
- $handlerB = TestContentStorageHandler::from($handlerA);
+ $handlerB = TestStorage::from($handlerA);
$this->assertNotSame($handlerA, $handlerB);
- $this->assertSame($latestEN, $handlerB->read($versionPublished, $en));
- $this->assertSame($latestDE, $handlerB->read($versionPublished, $de));
+ $this->assertSame($LatestEN, $handlerB->read($versionLatest, $en));
+ $this->assertSame($LatestDE, $handlerB->read($versionLatest, $de));
$this->assertSame($changesEN, $handlerB->read($versionChanges, $en));
$this->assertSame($changesDE, $handlerB->read($versionChanges, $de));
@@ -358,9 +338,7 @@ public function testModel()
{
$this->setUpSingleLanguage();
- $handler = new TestContentStorageHandler(
- model: $this->model
- );
+ $handler = new TestStorage(model: $this->model);
$this->assertSame($this->model, $handler->model());
}
@@ -372,9 +350,7 @@ public function testMoveMultiLanguage()
{
$this->setUpMultiLanguage();
- $handler = new TestContentStorageHandler(
- model: $this->model
- );
+ $handler = new TestStorage(model: $this->model);
$en = $this->app->language('en');
$de = $this->app->language('de');
@@ -401,9 +377,8 @@ public function testMoveSingleLanguage()
{
$this->setUpSingleLanguage();
- $handler = new TestContentStorageHandler(
- model: $this->model
- );
+ $handler = new TestStorage(model: $this->model);
+
$handler->create(VersionId::latest(), Language::single(), []);
@@ -427,13 +402,8 @@ public function testMovetoAnotherStorage()
{
$this->setUpSingleLanguage();
- $handler1 = new TestContentStorageHandler(
- model: $this->model
- );
-
- $handler2 = new TestContentStorageHandler(
- model: $this->model
- );
+ $handler1 = new TestStorage(model: $this->model);
+ $handler2 = new TestStorage(model: $this->model);
$handler1->create(VersionId::latest(), Language::single(), []);
@@ -457,13 +427,8 @@ public function testMoveAll()
{
$this->setUpSingleLanguage();
- $handler1 = new TestContentStorageHandler(
- model: $this->model
- );
-
- $handler2 = new TestContentStorageHandler(
- model: $this->model
- );
+ $handler1 = new TestStorage(model: $this->model);
+ $handler2 = new TestStorage(model: $this->model);
$handler1->create(VersionId::latest(), Language::single(), []);
$handler1->create(VersionId::changes(), Language::single(), []);
@@ -490,14 +455,12 @@ public function testMoveSingleLanguageToMultiLanguage()
// Use the plain text handler, as it offers the most
// realistic, testable results for this test
- $handler = new PlainTextContentStorageHandler(
- model: $this->model
- );
+ $handler = new PlainTextStorage(model: $this->model);
- Data::write($filePublished = $this->model->root() . '/article.txt', []);
+ Data::write($fileLatest = $this->model->root() . '/article.txt', []);
Data::write($fileChanges = $this->model->root() . '/_changes/article.txt', []);
- $this->assertFileExists($filePublished);
+ $this->assertFileExists($fileLatest);
$this->assertFileExists($fileChanges);
$handler->moveLanguage(
@@ -505,7 +468,7 @@ public function testMoveSingleLanguageToMultiLanguage()
$this->app->language('en')
);
- $this->assertFileDoesNotExist($filePublished);
+ $this->assertFileDoesNotExist($fileLatest);
$this->assertFileDoesNotExist($fileChanges);
$this->assertFileExists($this->model->root() . '/article.en.txt');
@@ -521,14 +484,13 @@ public function testMoveMultiLanguageToSingleLanguage()
// Use the plain text handler, as it offers the most
// realistic, testable results for this test
- $handler = new PlainTextContentStorageHandler(
- model: $this->model
- );
+ $handler = new PlainTextStorage(model: $this->model);
- Data::write($filePublished = $this->model->root() . '/article.en.txt', []);
+
+ Data::write($fileLatest = $this->model->root() . '/article.en.txt', []);
Data::write($fileChanges = $this->model->root() . '/_changes/article.en.txt', []);
- $this->assertFileExists($filePublished);
+ $this->assertFileExists($fileLatest);
$this->assertFileExists($fileChanges);
$handler->moveLanguage(
@@ -536,7 +498,7 @@ public function testMoveMultiLanguageToSingleLanguage()
Language::single(),
);
- $this->assertFileDoesNotExist($filePublished);
+ $this->assertFileDoesNotExist($fileLatest);
$this->assertFileDoesNotExist($fileChanges);
$this->assertFileExists($this->model->root() . '/article.txt');
@@ -553,9 +515,7 @@ public function testReplaceStrings()
$versionId = VersionId::changes();
$language = $this->app->language('en');
- $handler = new TestContentStorageHandler(
- model: $this->model
- );
+ $handler = new TestStorage(model: $this->model);
$fields = [
'foo' => 'one step',
@@ -584,17 +544,15 @@ public function testTouchLanguageMultiLanguage()
// Use the plain text handler, as the abstract class and the test handler do not
// implement the necessary methods to test this.
- $handler = new PlainTextContentStorageHandler(
- model: $this->model
- );
+ $handler = new PlainTextStorage(model: $this->model);
Dir::make($this->model->root());
Dir::make($this->model->root() . '/_changes');
- touch($filePublished = $this->model->root() . '/article.de.txt', 123456);
+ touch($fileLatest = $this->model->root() . '/article.de.txt', 123456);
touch($fileChanges = $this->model->root() . '/_changes/article.de.txt', 123456);
- $this->assertSame(123456, filemtime($filePublished));
+ $this->assertSame(123456, filemtime($fileLatest));
$this->assertSame(123456, filemtime($fileChanges));
$minTime = time();
@@ -604,7 +562,7 @@ public function testTouchLanguageMultiLanguage()
clearstatcache();
$this->assertGreaterThanOrEqual($minTime, filemtime($fileChanges));
- $this->assertGreaterThanOrEqual($minTime, filemtime($filePublished));
+ $this->assertGreaterThanOrEqual($minTime, filemtime($fileLatest));
}
}
diff --git a/tests/Content/TestCase.php b/tests/Content/TestCase.php
index 02183e31db..8552aa2a2e 100644
--- a/tests/Content/TestCase.php
+++ b/tests/Content/TestCase.php
@@ -85,9 +85,18 @@ public function setUpMultiLanguage(
parent::setUpMultiLanguage(
site: $site ?? [
'children' => [
+ [
+ 'slug' => 'home',
+ 'template' => 'default'
+ ],
[
'slug' => 'a-page',
- 'template' => 'article'
+ 'template' => 'article',
+ 'files' => [
+ [
+ 'filename' => 'a-file.jpg'
+ ]
+ ]
]
]
]
@@ -104,9 +113,18 @@ public function setUpSingleLanguage(
parent::setUpSingleLanguage(
site: $site ?? [
'children' => [
+ [
+ 'slug' => 'home',
+ 'template' => 'default'
+ ],
[
'slug' => 'a-page',
- 'template' => 'article'
+ 'template' => 'article',
+ 'files' => [
+ [
+ 'filename' => 'a-file.jpg'
+ ]
+ ]
]
]
]
diff --git a/tests/Content/TestContentStorageHandler.php b/tests/Content/TestStorage.php
similarity index 94%
rename from tests/Content/TestContentStorageHandler.php
rename to tests/Content/TestStorage.php
index 451fb4b38d..cb1ae6d9c2 100644
--- a/tests/Content/TestContentStorageHandler.php
+++ b/tests/Content/TestStorage.php
@@ -6,7 +6,7 @@
use Kirby\Cms\Language;
use Kirby\Cms\ModelWithContent;
-class TestContentStorageHandler extends ContentStorageHandler
+class TestStorage extends Storage
{
public array $store = [];
diff --git a/tests/Content/TranslationTest.php b/tests/Content/TranslationTest.php
index d2bebfa664..6684945bcc 100644
--- a/tests/Content/TranslationTest.php
+++ b/tests/Content/TranslationTest.php
@@ -154,7 +154,7 @@ public function testCreateWithSlug()
slug: 'foo'
);
- $this->assertSame(['title' => 'Test', 'slug' => 'foo'], $translation->version()->content()->toArray());
+ $this->assertSame(['title' => 'Test', 'slug' => 'foo'], $translation->version()->read());
$this->assertSame('foo', $translation->slug());
}
diff --git a/tests/Content/VersionIdTest.php b/tests/Content/VersionIdTest.php
index bf9b64525d..9921305847 100644
--- a/tests/Content/VersionIdTest.php
+++ b/tests/Content/VersionIdTest.php
@@ -2,6 +2,7 @@
namespace Kirby\Content;
+use Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\TestCase;
@@ -10,6 +11,13 @@
*/
class VersionIdTest extends TestCase
{
+ public function tearDown(): void
+ {
+ parent::tearDown();
+
+ VersionId::$render = null;
+ }
+
/**
* @covers ::all
*/
@@ -81,13 +89,121 @@ public function testIs()
* @covers ::latest
* @covers ::value
*/
- public function testPublished()
+ public function testLatest()
{
$version = VersionId::latest();
$this->assertSame('latest', $version->value());
}
+ /**
+ * @covers ::render
+ */
+ public function testRenderString()
+ {
+ $executed = 0;
+
+ $this->assertNull(VersionId::$render);
+
+ $return = VersionId::render('latest', function () use (&$executed) {
+ $executed++;
+ $this->assertSame('latest', VersionId::$render->value());
+
+ return 'some string';
+ });
+ $this->assertSame('some string', $return);
+
+ $this->assertNull(VersionId::$render);
+
+ $return = VersionId::render('changes', function () use (&$executed) {
+ $executed += 2;
+ $this->assertSame('changes', VersionId::$render->value());
+
+ return 12345;
+ });
+ $this->assertSame(12345, $return);
+
+ $this->assertNull(VersionId::$render);
+ $this->assertSame(3, $executed);
+ }
+
+ /**
+ * @covers ::render
+ */
+ public function testRenderInstance()
+ {
+ $executed = 0;
+
+ $this->assertNull(VersionId::$render);
+
+ $return = VersionId::render(VersionId::latest(), function () use (&$executed) {
+ $executed++;
+ $this->assertSame('latest', VersionId::$render->value());
+
+ return 'some string';
+ });
+ $this->assertSame('some string', $return);
+
+ $this->assertNull(VersionId::$render);
+
+ $return = VersionId::render(VersionId::changes(), function () use (&$executed) {
+ $executed += 2;
+ $this->assertSame('changes', VersionId::$render->value());
+
+ return 12345;
+ });
+ $this->assertSame(12345, $return);
+
+ $this->assertNull(VersionId::$render);
+ $this->assertSame(3, $executed);
+ }
+
+ /**
+ * @covers ::render
+ */
+ public function testRenderPreviousValue()
+ {
+ $executed = 0;
+
+ VersionId::$render = VersionId::latest();
+
+ $return = VersionId::render('changes', function () use (&$executed) {
+ $executed++;
+ $this->assertSame('changes', VersionId::$render->value());
+
+ return 'some string';
+ });
+ $this->assertSame('some string', $return);
+
+ $this->assertSame('latest', VersionId::$render->value());
+ $this->assertSame(1, $executed);
+ }
+
+ /**
+ * @covers ::render
+ */
+ public function testRenderException()
+ {
+ $executed = 0;
+
+ $this->assertNull(VersionId::$render);
+
+ try {
+ VersionId::render(VersionId::latest(), function () use (&$executed) {
+ $executed++;
+ $this->assertSame('latest', VersionId::$render->value());
+
+ throw new Exception('Something went wrong');
+ });
+ } catch (Exception $e) {
+ $executed += 2;
+ $this->assertSame('Something went wrong', $e->getMessage());
+ }
+
+ $this->assertNull(VersionId::$render);
+ $this->assertSame(3, $executed);
+ }
+
/**
* @covers ::__toString
*/
diff --git a/tests/Content/VersionRulesTest.php b/tests/Content/VersionRulesTest.php
new file mode 100644
index 0000000000..ee0932a514
--- /dev/null
+++ b/tests/Content/VersionRulesTest.php
@@ -0,0 +1,353 @@
+setUpSingleLanguage();
+
+ $version = new ExistingVersion(
+ model: $this->model,
+ id: VersionId::latest(),
+ );
+
+ $this->expectException(LogicException::class);
+ $this->expectExceptionMessage('The version already exists');
+
+ VersionRules::create($version, [], Language::ensure());
+ }
+
+ /**
+ * @covers ::create
+ */
+ public function testCreateWhenLatestVersionDoesNotExist()
+ {
+ $this->setUpSingleLanguage();
+
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::changes(),
+ );
+
+ // remove the model root to simulate a missing latest version
+ Dir::remove($this->model->root());
+
+ $this->expectException(LogicException::class);
+ $this->expectExceptionMessage('A matching latest version for the changes does not exist');
+
+ VersionRules::create($version, [], Language::ensure());
+ }
+
+ /**
+ * @covers ::delete
+ */
+ public function testDeleteWhenTheVersionIsLocked()
+ {
+ $this->setUpSingleLanguage();
+
+ $version = new LockedVersion(
+ model: $this->model,
+ id: VersionId::changes(),
+ );
+
+ $this->expectException(LockedContentException::class);
+ $this->expectExceptionCode('error.content.lock.delete');
+
+ VersionRules::delete($version, Language::ensure());
+ }
+
+ /**
+ * @covers ::ensure
+ */
+ public function testEnsureMultiLanguage(): void
+ {
+ $this->setUpMultiLanguage();
+
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::latest()
+ );
+
+ $this->createContentMultiLanguage();
+
+ $this->assertNull(VersionRules::ensure($version, Language::ensure('en')));
+ $this->assertNull(VersionRules::ensure($version, Language::ensure('de')));
+ }
+
+ /**
+ * @covers ::ensure
+ */
+ public function testEnsureSingleLanguage(): void
+ {
+ $this->setUpSingleLanguage();
+
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::latest()
+ );
+
+ $this->createContentSingleLanguage();
+
+ $this->assertNull(VersionRules::ensure($version, Language::ensure()));
+ }
+
+ /**
+ * @covers ::ensure
+ */
+ public function testEnsureWhenMissingMultiLanguage(): void
+ {
+ $this->setUpMultiLanguage();
+
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::changes()
+ );
+
+ $this->expectException(NotFoundException::class);
+ $this->expectExceptionMessage('Version "changes (de)" does not already exist');
+
+ VersionRules::ensure($version, Language::ensure('de'));
+ }
+
+ /**
+ * @covers ::ensure
+ */
+ public function testEnsureWhenMissingSingleLanguage(): void
+ {
+ $this->setUpSingleLanguage();
+
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::changes()
+ );
+
+ $this->expectException(NotFoundException::class);
+ $this->expectExceptionMessage('Version "changes" does not already exist');
+
+ VersionRules::ensure($version, Language::ensure());
+ }
+
+ /**
+ * @covers ::ensure
+ */
+ public function testEnsureWithInvalidLanguage(): void
+ {
+ $this->setUpMultiLanguage();
+
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::latest()
+ );
+
+ $this->expectException(NotFoundException::class);
+ $this->expectExceptionMessage('Invalid language: fr');
+
+ VersionRules::ensure($version, Language::ensure('fr'));
+ }
+
+ /**
+ * @covers ::move
+ */
+ public function testMoveWhenTheSourceVersionIsLocked()
+ {
+ $this->setUpSingleLanguage();
+
+ $source = new LockedVersion(
+ model: $this->model,
+ id: VersionId::changes(),
+ );
+
+ $target = new Version(
+ model: $this->model,
+ id: VersionId::changes(),
+ );
+
+ $source->save([]);
+ $target->save([]);
+
+ $this->expectException(LockedContentException::class);
+ $this->expectExceptionCode('error.content.lock.move');
+
+ VersionRules::move($source, Language::ensure(), $target, Language::ensure());
+ }
+
+ /**
+ * @covers ::move
+ */
+ public function testMoveWhenTheTargetVersionIsLocked()
+ {
+ $this->setUpSingleLanguage();
+
+ $source = new Version(
+ model: $this->model,
+ id: VersionId::changes(),
+ );
+
+ $target = new LockedVersion(
+ model: $this->model,
+ id: VersionId::changes(),
+ );
+
+ // the source needs to exist to jump to the
+ // next logic issue
+ $source->save([]);
+
+ $this->expectException(LockedContentException::class);
+ $this->expectExceptionCode('error.content.lock.update');
+
+ VersionRules::move($source, Language::ensure(), $target, Language::ensure());
+ }
+
+ /**
+ * @covers ::publish
+ */
+ public function testPublishTheLatestVersion()
+ {
+ $this->setUpSingleLanguage();
+
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::latest(),
+ );
+
+ $this->expectException(LogicException::class);
+ $this->expectExceptionMessage('This version is already published');
+
+ VersionRules::publish($version, Language::ensure());
+ }
+
+ /**
+ * @covers ::publish
+ */
+ public function testPublishWhenTheVersionIsLocked()
+ {
+ $this->setUpSingleLanguage();
+
+ $version = new LockedVersion(
+ model: $this->model,
+ id: VersionId::changes(),
+ );
+
+ $version->save([]);
+
+ $this->expectException(LockedContentException::class);
+ $this->expectExceptionCode('error.content.lock.publish');
+
+ VersionRules::publish($version, Language::ensure());
+ }
+
+ /**
+ * @covers ::read
+ */
+ public function testReadWhenMissing()
+ {
+ $this->setUpSingleLanguage();
+
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::latest(),
+ );
+
+ // make sure that the version does not exist
+ // just because the model root exists
+ Dir::remove($this->model->root());
+
+ $this->expectException(NotFoundException::class);
+ $this->expectExceptionMessage('Version "latest" does not already exist');
+
+ VersionRules::read($version, Language::ensure());
+ }
+
+ /**
+ * @covers ::replace
+ */
+ public function testReplaceWhenTheVersionIsLocked()
+ {
+ $this->setUpSingleLanguage();
+
+ $version = new LockedVersion(
+ model: $this->model,
+ id: VersionId::changes(),
+ );
+
+ $version->save([]);
+
+ $this->expectException(LockedContentException::class);
+ $this->expectExceptionCode('error.content.lock.replace');
+
+ VersionRules::replace($version, [], Language::ensure());
+ }
+
+ /**
+ * @covers ::touch
+ */
+ public function testTouchWhenMissing()
+ {
+ $this->setUpSingleLanguage();
+
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::latest(),
+ );
+
+ // make sure that the version does not exist
+ // just because the model root exists
+ Dir::remove($this->model->root());
+
+ $this->expectException(NotFoundException::class);
+ $this->expectExceptionMessage('Version "latest" does not already exist');
+
+ VersionRules::touch($version, Language::ensure());
+ }
+
+ /**
+ * @covers ::update
+ */
+ public function testUpdateWhenTheVersionIsLocked()
+ {
+ $this->setUpSingleLanguage();
+
+ $version = new LockedVersion(
+ model: $this->model,
+ id: VersionId::changes(),
+ );
+
+ $version->save([]);
+
+ $this->expectException(LockedContentException::class);
+ $this->expectExceptionCode('error.content.lock.update');
+
+ VersionRules::update($version, [], Language::ensure());
+ }
+}
diff --git a/tests/Content/VersionTest.php b/tests/Content/VersionTest.php
index a7cff3a275..bb64cf2eae 100644
--- a/tests/Content/VersionTest.php
+++ b/tests/Content/VersionTest.php
@@ -2,6 +2,10 @@
namespace Kirby\Content;
+use Kirby\Cms\App;
+use Kirby\Cms\File;
+use Kirby\Cms\Page;
+use Kirby\Cms\Site;
use Kirby\Data\Data;
use Kirby\Exception\LogicException;
use Kirby\Exception\NotFoundException;
@@ -77,6 +81,53 @@ public function testContentWithFallback(): void
$this->assertSame($version->content('en')->toArray(), $version->content('de')->toArray());
}
+ /**
+ * @covers ::content
+ * @covers ::prepareFieldsForContent
+ */
+ public function testContentPrepareFields(): void
+ {
+ $this->setUpSingleLanguage();
+
+ // for pages
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::latest()
+ );
+
+ $version->update([
+ 'lock' => 'test',
+ 'slug' => 'foo',
+ 'text' => 'Lorem ipsum'
+ ]);
+
+ $this->assertSame([
+ 'text' => 'Lorem ipsum'
+ ], $version->content()->toArray());
+
+ // for files
+ $model = new File([
+ 'filename' => 'test.jpg',
+ 'parent' => $this->model
+ ]);
+
+ $version = new Version(
+ model: $model,
+ id: VersionId::latest()
+ );
+
+ $version->create([
+ 'lock' => 'test',
+ 'template' => 'foo',
+ 'text' => 'Lorem ipsum'
+ ]);
+
+ $this->assertSame([
+ 'template' => 'foo',
+ 'text' => 'Lorem ipsum',
+ ], $version->content()->toArray());
+ }
+
/**
* @covers ::contentFile
*/
@@ -127,12 +178,12 @@ public function testCreateMultiLanguage(): void
$this->assertContentFileDoesNotExist('de');
// with Language argument
- $version->create([
+ $version->save([
'title' => 'Test'
], $this->app->language('en'));
// with string argument
- $version->create([
+ $version->save([
'title' => 'Test'
], 'de');
@@ -140,6 +191,44 @@ public function testCreateMultiLanguage(): void
$this->assertContentFileExists('de');
}
+ /**
+ * @covers ::create
+ */
+ public function testCreateMultiLanguageWhenLatestTranslationIsMissing(): void
+ {
+ $this->setUpMultiLanguage();
+
+ $latest = new Version(
+ model: $this->model,
+ id: VersionId::latest()
+ );
+
+ $changes = new Version(
+ model: $this->model,
+ id: VersionId::changes()
+ );
+
+ $this->assertContentFileDoesNotExist('en', $latest->id());
+ $this->assertContentFileDoesNotExist('en', $changes->id());
+ $this->assertContentFileDoesNotExist('de', $latest->id());
+ $this->assertContentFileDoesNotExist('de', $changes->id());
+
+ // create the latest version for the default translation
+ $latest->save([
+ 'title' => 'Test'
+ ], $this->app->language('en'));
+
+ // create a changes version in the other language
+ $changes->save([
+ 'title' => 'Translated Test',
+ ], $this->app->language('de'));
+
+ $this->assertContentFileExists('en', $latest->id());
+ $this->assertContentFileDoesNotExist('en', $changes->id());
+ $this->assertContentFileExists('de', $latest->id());
+ $this->assertContentFileExists('de', $changes->id());
+ }
+
/**
* @covers ::create
*/
@@ -154,7 +243,7 @@ public function testCreateSingleLanguage(): void
$this->assertContentFileDoesNotExist();
- $version->create([
+ $version->save([
'title' => 'Test'
]);
@@ -190,7 +279,7 @@ public function testCreateWithDirtyFields(): void
);
// primary language
- $version->create([
+ $version->save([
'title' => 'Test',
'uuid' => '12345',
'Subtitle' => 'Subtitle',
@@ -238,7 +327,12 @@ public function testDeleteMultiLanguage(): void
$this->assertContentFileExists('en');
$this->assertContentFileExists('de');
- $version->delete();
+ $version->delete('en');
+
+ $this->assertContentFileDoesNotExist('en');
+ $this->assertContentFileExists('de');
+
+ $version->delete('de');
$this->assertContentFileDoesNotExist('en');
$this->assertContentFileDoesNotExist('de');
@@ -268,265 +362,220 @@ public function testDeleteSingleLanguage(): void
}
/**
- * @covers ::diff
+ * @covers ::exists
*/
- public function testDiffMultiLanguage()
+ public function testExistsLatestMultiLanguage(): void
{
$this->setUpMultiLanguage();
- $a = new Version(
+ $version = new Version(
model: $this->model,
id: VersionId::latest()
);
- $b = new Version(
- model: $this->model,
- id: VersionId::changes()
- );
-
- $a->create($content = [
- 'title' => 'Title',
- 'subtitle' => 'Subtitle',
- ], 'en');
-
- $a->create($content, 'de');
-
- $b->create($content, 'en');
-
- $b->create([
- 'title' => 'Title',
- 'subtitle' => 'Subtitle (changed)',
- ], 'de');
+ $this->assertDirectoryExists($this->model->root());
- // no changes in English
- $diffEN = $a->diff(VersionId::changes(), 'en');
- $expectedEN = [];
+ // the default version + default language exists without
+ // content file as long as the page directory exists
+ $this->assertTrue($version->exists('en'));
+ $this->assertTrue($version->exists($this->app->language('en')));
- $this->assertSame($expectedEN, $diffEN);
+ // the secondary language only exists as soon as the content
+ // file also exists
+ $this->assertFalse($version->exists('de'));
+ $this->assertFalse($version->exists($this->app->language('de')));
- // changed subtitle in German
- $diffDE = $a->diff(VersionId::changes(), 'de');
- $expectedDE = ['subtitle' => 'Subtitle (changed)'];
+ $this->createContentMultiLanguage();
- $this->assertSame($expectedDE, $diffDE);
+ $this->assertTrue($version->exists('de'));
+ $this->assertTrue($version->exists($this->app->language('de')));
}
/**
- * @covers ::diff
+ * @covers ::exists
*/
- public function testDiffSingleLanguage()
+ public function testExistsWithLanguageWildcard(): void
{
- $this->setUpSingleLanguage();
+ $this->setUpMultiLanguage();
- $a = new Version(
+ $version = new Version(
model: $this->model,
id: VersionId::latest()
);
- $b = new Version(
- model: $this->model,
- id: VersionId::changes()
- );
-
- $a->create([
- 'title' => 'Title',
- 'subtitle' => 'Subtitle',
- ]);
+ $this->createContentMultiLanguage();
- $b->create([
- 'title' => 'Title',
- 'subtitle' => 'Subtitle (changed)',
- ]);
+ $this->assertTrue($version->exists('en'));
+ $this->assertTrue($version->exists('de'));
+ $this->assertTrue($version->exists('*'));
- $diff = $a->diff(VersionId::changes());
+ // delete the German translation
+ $version->delete('de');
- // the result array should contain the changed fields
- // the changed values
- $expected = ['subtitle' => 'Subtitle (changed)'];
+ $this->assertTrue($version->exists('en'));
+ $this->assertFalse($version->exists('de'));
- $this->assertSame($expected, $diff);
+ // The wildcard should now still return true
+ // because the English translation still exists
+ $this->assertTrue($version->exists('*'));
}
/**
- * @covers ::diff
+ * @covers ::exists
*/
- public function testDiffWithoutChanges()
+ public function testExistsLatestSingleLanguage(): void
{
$this->setUpSingleLanguage();
- $a = new Version(
+ $version = new Version(
model: $this->model,
id: VersionId::latest()
);
- $b = new Version(
- model: $this->model,
- id: VersionId::changes()
- );
-
- $a->create([
- 'title' => 'Title',
- 'subtitle' => 'Subtitle',
- ]);
-
- $b->create([
- 'title' => 'Title',
- 'subtitle' => 'Subtitle',
- ]);
-
- $diff = $a->diff(VersionId::changes());
+ $this->assertDirectoryExists($this->model->root());
- $this->assertSame([], $diff);
+ // the default version exists without content file as long as
+ // the page directory exists
+ $this->assertTrue($version->exists());
}
/**
- * @covers ::diff
+ * @covers ::id
*/
- public function testDiffWithSameVersion()
+ public function testId(): void
{
$this->setUpSingleLanguage();
- $a = new Version(
+ $version = new Version(
model: $this->model,
- id: VersionId::latest()
+ id: $id = VersionId::latest()
);
- $a->create([
- 'title' => 'Title',
- 'subtitle' => 'Subtitle',
- ]);
-
- $diff = $a->diff(VersionId::latest());
-
- $this->assertSame([], $diff);
+ $this->assertSame($id, $version->id());
}
/**
- * @covers ::ensure
+ * @covers ::isIdentical
*/
- public function testEnsureMultiLanguage(): void
+ public function testIsIdenticalMultiLanguage()
{
$this->setUpMultiLanguage();
- $version = new Version(
+ $a = new Version(
model: $this->model,
id: VersionId::latest()
);
- $this->createContentMultiLanguage();
+ $b = new Version(
+ model: $this->model,
+ id: VersionId::changes()
+ );
+
+ $a->save($content = [
+ 'title' => 'Title',
+ 'subtitle' => 'Subtitle',
+ ], 'en');
+
+ $a->save($content, 'de');
+
+ $b->save($content, 'en');
+
+ $b->save([
+ 'title' => 'Title',
+ 'subtitle' => 'Subtitle (changed)',
+ ], 'de');
- $this->assertNull($version->ensure('en'));
- $this->assertNull($version->ensure($this->app->language('en')));
+ // no changes in English
+ $this->assertTrue($a->isIdentical(VersionId::changes(), 'en'));
- $this->assertNull($version->ensure('de'));
- $this->assertNull($version->ensure($this->app->language('de')));
+ // changed subtitle in German
+ $this->assertFalse($a->isIdentical(VersionId::changes(), 'de'));
}
/**
- * @covers ::ensure
+ * @covers ::isIdentical
*/
- public function testEnsureSingleLanguage(): void
+ public function testIsIdenticalSingleLanguage()
{
$this->setUpSingleLanguage();
- $version = new Version(
+ $a = new Version(
model: $this->model,
id: VersionId::latest()
);
- $this->createContentSingleLanguage();
-
- $this->assertNull($version->ensure());
- }
-
- /**
- * @covers ::ensure
- */
- public function testEnsureWhenMissingMultiLanguage(): void
- {
- $this->setUpMultiLanguage();
-
- $version = new Version(
+ $b = new Version(
model: $this->model,
id: VersionId::changes()
);
- $this->expectException(NotFoundException::class);
- $this->expectExceptionMessage('Version "changes (de)" does not already exist');
+ $a->save($content = [
+ 'title' => 'Title',
+ 'subtitle' => 'Subtitle',
+ ]);
- $version->ensure('de');
+ $b->save([
+ 'title' => 'Title',
+ 'subtitle' => 'Subtitle (changed)',
+ ]);
+
+ $this->assertFalse($a->isIdentical('changes'));
}
/**
- * @covers ::ensure
+ * @covers ::isIdentical
*/
- public function testEnsureWhenMissingSingleLanguage(): void
+ public function testIsIdenticalWithoutChanges()
{
$this->setUpSingleLanguage();
- $version = new Version(
+ $a = new Version(
model: $this->model,
- id: VersionId::changes()
+ id: VersionId::latest()
);
- $this->expectException(NotFoundException::class);
- $this->expectExceptionMessage('Version "changes" does not already exist');
-
- $version->ensure();
- }
-
- /**
- * @covers ::ensure
- */
- public function testEnsureWithInvalidLanguage(): void
- {
- $this->setUpMultiLanguage();
-
- $version = new Version(
+ $b = new Version(
model: $this->model,
- id: VersionId::latest()
+ id: VersionId::changes()
);
- $this->expectException(NotFoundException::class);
- $this->expectExceptionMessage('Invalid language: fr');
+ $a->save([
+ 'title' => 'Title',
+ 'subtitle' => 'Subtitle',
+ ]);
- $version->ensure('fr');
+ $b->save([
+ 'title' => 'Title',
+ 'subtitle' => 'Subtitle',
+ ]);
+
+ $this->assertTrue($a->isIdentical('changes'));
}
/**
- * @covers ::exists
+ * @covers ::isIdentical
*/
- public function testExistsPublishedMultiLanguage(): void
+ public function testIsIdenticalWithSameVersion()
{
- $this->setUpMultiLanguage();
+ $this->setUpSingleLanguage();
- $version = new Version(
+ $a = new Version(
model: $this->model,
id: VersionId::latest()
);
- $this->assertDirectoryExists($this->model->root());
-
- // the default version + default language exists without
- // content file as long as the page directory exists
- $this->assertTrue($version->exists('en'));
- $this->assertTrue($version->exists($this->app->language('en')));
-
- // the secondary language only exists as soon as the content
- // file also exists
- $this->assertFalse($version->exists('de'));
- $this->assertFalse($version->exists($this->app->language('de')));
-
- $this->createContentMultiLanguage();
+ $a->save([
+ 'title' => 'Title',
+ 'subtitle' => 'Subtitle',
+ ]);
- $this->assertTrue($version->exists('de'));
- $this->assertTrue($version->exists($this->app->language('de')));
+ $this->assertTrue($a->isIdentical('latest'));
}
/**
- * @covers ::exists
+ * @covers ::isLocked
*/
- public function testExistsPublishedSingleLanguage(): void
+ public function testIsLocked(): void
{
$this->setUpSingleLanguage();
@@ -535,26 +584,22 @@ public function testExistsPublishedSingleLanguage(): void
id: VersionId::latest()
);
- $this->assertDirectoryExists($this->model->root());
-
- // the default version exists without content file as long as
- // the page directory exists
- $this->assertTrue($version->exists());
+ $this->assertFalse($version->isLocked());
}
/**
- * @covers ::id
+ * @covers ::lock
*/
- public function testId(): void
+ public function testLock(): void
{
$this->setUpSingleLanguage();
$version = new Version(
model: $this->model,
- id: $id = VersionId::latest()
+ id: VersionId::latest()
);
- $this->assertSame($id, $version->id());
+ $this->assertInstanceOf(Lock::class, $version->lock());
}
/**
@@ -689,9 +734,9 @@ public function testMoveToVersion(): void
{
$this->setUpMultiLanguage();
- $versionPublished = new Version(
+ $versionLatest = new Version(
model: $this->model,
- id: $versionIdPublished = VersionId::latest()
+ id: $versionIdLatest = VersionId::latest()
);
$versionChanges = new Version(
@@ -699,34 +744,150 @@ public function testMoveToVersion(): void
id: $versionIdChanges = VersionId::changes()
);
- $this->assertContentFileDoesNotExist('en', $versionIdPublished);
+ $this->assertContentFileDoesNotExist('en', $versionIdLatest);
$this->assertContentFileDoesNotExist('en', $versionIdChanges);
- $fileENPublished = $this->contentFile('en', $versionIdPublished);
+ $fileENLatest = $this->contentFile('en', $versionIdLatest);
$fileENChanges = $this->contentFile('en', $versionIdChanges);
- Data::write($fileENPublished, $content = [
+ Data::write($fileENLatest, $content = [
'title' => 'Test'
]);
- $this->assertContentFileExists('en', $versionIdPublished);
+ $this->assertContentFileExists('en', $versionIdLatest);
$this->assertContentFileDoesNotExist('en', $versionIdChanges);
// move with string arguments
- $versionPublished->move('en', $versionIdChanges);
+ $versionLatest->move('en', $versionIdChanges);
- $this->assertContentFileDoesNotExist('en', $versionIdPublished);
+ $this->assertContentFileDoesNotExist('en', $versionIdLatest);
$this->assertContentFileExists('en', $versionIdChanges);
$this->assertSame($content, Data::read($fileENChanges));
// move the version back
- $versionChanges->move('en', $versionIdPublished);
+ $versionChanges->move('en', $versionIdLatest);
$this->assertContentFileDoesNotExist('en', $versionIdChanges);
- $this->assertContentFileExists('en', $versionIdPublished);
+ $this->assertContentFileExists('en', $versionIdLatest);
+
+ $this->assertSame($content, Data::read($fileENLatest));
+ }
+
+ /**
+ * @covers ::previewToken
+ */
+ public function testPreviewToken()
+ {
+ $this->setUpSingleLanguage();
+
+ // site
+ $version = new Version(
+ model: $this->app->site(),
+ id: VersionId::latest()
+ );
+ $expected = substr(hash_hmac('sha1', '{"uri":"","versionId":"latest"}', static::TMP . '/content'), 0, 10);
+ $this->assertSame($expected, $version->previewToken());
+
+ // page
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::latest()
+ );
+ $expected = substr(hash_hmac('sha1', '{"uri":"a-page","versionId":"latest"}', static::TMP . '/content'), 0, 10);
+ $this->assertSame($expected, $version->previewToken());
+ }
+
+ /**
+ * @covers ::previewToken
+ */
+ public function testPreviewTokenCustomSalt()
+ {
+ $this->setUpSingleLanguage();
+
+ $this->app->clone([
+ 'options' => [
+ 'content' => [
+ 'salt' => 'testsalt'
+ ]
+ ]
+ ]);
+
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::latest()
+ );
- $this->assertSame($content, Data::read($fileENPublished));
+ $expected = substr(hash_hmac('sha1', '{"uri":"a-page","versionId":"latest"}', 'testsalt'), 0, 10);
+ $this->assertSame($expected, $version->previewToken());
+ }
+
+ /**
+ * @covers ::previewToken
+ */
+ public function testPreviewTokenCustomSaltCallback()
+ {
+ $this->setUpSingleLanguage();
+
+ $this->app = $this->app->clone([
+ 'options' => [
+ 'content' => [
+ 'salt' => function ($model) {
+ $this->assertNull($model);
+
+ return 'salt-lake-city';
+ }
+ ]
+ ]
+ ]);
+
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::latest()
+ );
+
+ $expected = substr(hash_hmac('sha1', '{"uri":"a-page","versionId":"latest"}', 'salt-lake-city'), 0, 10);
+ $this->assertSame($expected, $version->previewToken());
+ }
+
+ /**
+ * @covers ::previewToken
+ */
+ public function testPreviewTokenInvalidModel()
+ {
+ $this->expectException(LogicException::class);
+ $this->expectExceptionMessage('Invalid model type');
+
+ $this->setUpSingleLanguage();
+
+ $version = new Version(
+ model: $this->model->file(),
+ id: VersionId::latest()
+ );
+
+ $version->previewToken();
+ }
+
+ /**
+ * @covers ::previewToken
+ */
+ public function testPreviewTokenMissingHomePage()
+ {
+ $this->expectException(NotFoundException::class);
+ $this->expectExceptionMessage('The home page does not exist');
+
+ $app = new App([
+ 'roots' => [
+ 'index' => static::TMP
+ ]
+ ]);
+
+ $version = new Version(
+ model: $app->site(),
+ id: VersionId::latest()
+ );
+
+ $version->previewToken();
}
/**
@@ -735,34 +896,35 @@ public function testMoveToVersion(): void
public function testPublish()
{
$this->setUpSingleLanguage();
+ $this->app->impersonate('kirby');
$version = new Version(
model: $this->model,
id: VersionId::changes()
);
- Data::write($filePublished = $this->contentFile(null, VersionId::latest()), [
- 'title' => 'Title latest'
+ Data::write($fileLatest = $this->contentFile(null, VersionId::latest()), [
+ 'title' => 'Title Latest'
]);
Data::write($fileChanges = $this->contentFile(null, VersionId::changes()), [
'title' => 'Title changes'
]);
- $this->assertFileExists($filePublished);
+ $this->assertFileExists($fileLatest);
$this->assertFileExists($fileChanges);
$version->publish();
$this->assertFileDoesNotExist($fileChanges);
- $this->assertSame('Title changes', Data::read($filePublished)['title']);
+ $this->assertSame('Title changes', Data::read($fileLatest)['title']);
}
/**
* @covers ::publish
*/
- public function testPublishAlreadyPublishedVersion()
+ public function testPublishAlreadyLatestVersion()
{
$this->setUpSingleLanguage();
@@ -823,7 +985,7 @@ public function testReadSingleLanguage(): void
/**
* @covers ::read
*/
- public function testReadPublishedWithoutContentFile(): void
+ public function testReadLatestWithoutContentFile(): void
{
$this->setUpSingleLanguage();
@@ -1311,4 +1473,309 @@ public function testUpdateWithDirtyFields(): void
$this->assertArrayHasKey('date', $version->read('en'));
$this->assertArrayNotHasKey('date', $version->read('de'));
}
+
+ /**
+ * @covers ::url
+ * @covers ::urlWithQueryParams
+ */
+ public function testUrlPage()
+ {
+ $this->setUpSingleLanguage();
+
+ // authenticate
+ $this->app->impersonate('kirby');
+
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::latest()
+ );
+
+ $this->assertSame('/a-page', $version->url());
+ }
+
+ /**
+ * @covers ::url
+ */
+ public function testUrlPageUnauthenticated()
+ {
+ $this->setUpSingleLanguage();
+
+ $version = new Version(
+ model: $this->model,
+ id: VersionId::latest()
+ );
+
+ $this->assertNull($version->url());
+ }
+
+ public static function pageUrlProvider(): array
+ {
+ return [
+ // latest version
+ [null, '/test', null, false, 'latest'],
+ [null, '/test?{token}', 'test', true, 'latest'],
+ [true, '/test', null, false, 'latest'],
+ [true, '/test?{token}', 'test', true, 'latest'],
+ ['https://test.com', 'https://test.com', null, false, 'latest'],
+ ['https://test.com', 'https://test.com', null, true, 'latest'],
+ ['/something/different', '/something/different', 'something\/different', false, 'latest'],
+ ['/something/different', '/something/different?{token}', 'something\/different', true, 'latest'],
+ ['{{ site.url }}#{{ page.slug }}', '/#test', null, false, 'latest'],
+ ['{{ site.url }}#{{ page.slug }}', '/?{token}#test', '', true, 'latest'],
+ ['{{ page.url }}?preview=true', '/test?preview=true', null, false, 'latest'],
+ ['{{ page.url }}?preview=true', '/test?preview=true&{token}', 'test', true, 'latest'],
+ ['{{ page.url }}/param:something', '/test/param:something', null, false, 'latest'],
+ ['{{ page.url }}/param:something', '/test/param:something?{token}', 'test', true, 'latest'],
+ [false, null, null, false, 'latest'],
+ [false, null, null, true, 'latest'],
+ [null, null, null, false, 'latest', false],
+
+ // changes version
+ [null, '/test?{token}&_version=changes', 'test', false, 'changes'],
+ [null, '/test?{token}&_version=changes', 'test', true, 'changes'],
+ [true, '/test?{token}&_version=changes', 'test', false, 'changes'],
+ [true, '/test?{token}&_version=changes', 'test', true, 'changes'],
+ ['https://test.com', 'https://test.com', null, false, 'changes'],
+ ['https://test.com', 'https://test.com', null, true, 'changes'],
+ ['/something/different', '/something/different?{token}&_version=changes', 'something\/different', false, 'changes'],
+ ['/something/different', '/something/different?{token}&_version=changes', 'something\/different', true, 'changes'],
+ ['{{ site.url }}#{{ page.slug }}', '/?{token}&_version=changes#test', '', false, 'changes'],
+ ['{{ site.url }}#{{ page.slug }}', '/?{token}&_version=changes#test', '', true, 'changes'],
+ ['{{ page.url }}?preview=true', '/test?preview=true&{token}&_version=changes', 'test', false, 'changes'],
+ ['{{ page.url }}?preview=true', '/test?preview=true&{token}&_version=changes', 'test', true, 'changes'],
+ ['{{ page.url }}/param:something', '/test/param:something?{token}&_version=changes', 'test', false, 'changes'],
+ ['{{ page.url }}/param:something', '/test/param:something?{token}&_version=changes', 'test', true, 'changes'],
+ [false, null, null, false, 'changes'],
+ [false, null, null, true, 'changes'],
+ [null, null, null, false, 'changes', false],
+ ];
+ }
+
+ /**
+ * @covers ::previewTokenFromUrl
+ * @covers ::url
+ * @covers ::urlFromOption
+ * @covers ::urlWithQueryParams
+ * @dataProvider pageUrlProvider
+ */
+ public function testUrlPageCustom(
+ $input,
+ $expected,
+ $expectedUri,
+ bool $draft,
+ string $versionId,
+ bool $authenticated = true
+ ): void {
+ $this->setUpSingleLanguage();
+
+ $app = $this->app->clone([
+ 'users' => [
+ [
+ 'id' => 'test',
+ 'email' => 'test@getkirby.com',
+ 'role' => 'editor'
+ ]
+ ],
+ 'roles' => [
+ [
+ 'id' => 'editor',
+ 'name' => 'editor',
+ ]
+ ]
+ ]);
+
+ // authenticate
+ if ($authenticated === true) {
+ $app->impersonate('test@getkirby.com');
+ }
+
+ $options = [];
+
+ if ($input !== null) {
+ $options = [
+ 'preview' => $input
+ ];
+ }
+
+ $page = new Page([
+ 'slug' => 'test',
+ 'isDraft' => $draft,
+ 'blueprint' => [
+ 'name' => 'test',
+ 'options' => $options
+ ]
+ ]);
+
+ if ($expected !== null) {
+ $expectedToken = substr(
+ hash_hmac(
+ 'sha1',
+ '{"uri":"' . $expectedUri . '","versionId":"' . $versionId . '"}',
+ $page->kirby()->root('content')
+ ),
+ 0,
+ 10
+ );
+ $expected = str_replace(
+ '{token}',
+ '_token=' . $expectedToken,
+ $expected
+ );
+ }
+
+ $version = new Version(
+ model: $page,
+ id: VersionId::from($versionId)
+ );
+
+ $this->assertSame($expected, $version->url());
+ }
+
+ /**
+ * @covers ::url
+ * @covers ::urlWithQueryParams
+ */
+ public function testUrlSite()
+ {
+ $this->setUpSingleLanguage();
+
+ // authenticate
+ $this->app->impersonate('kirby');
+
+ $version = new Version(
+ model: $this->app->site(),
+ id: VersionId::latest()
+ );
+
+ $this->assertSame('/', $version->url());
+ }
+
+ /**
+ * @covers ::url
+ */
+ public function testUrlSiteUnauthenticated()
+ {
+ $this->setUpSingleLanguage();
+
+ $version = new Version(
+ model: $this->app->site(),
+ id: VersionId::latest()
+ );
+
+ $this->assertNull($version->url());
+ }
+
+ public static function siteUrlProvider(): array
+ {
+ return [
+ // latest version
+ [null, '/', 'latest'],
+ ['https://test.com', 'https://test.com', 'latest'],
+ ['{{ site.url }}#test', '/#test', 'latest'],
+ [false, null, 'latest'],
+ [null, null, 'latest', false],
+
+ // changes version
+ [null, '/?{token}&_version=changes', 'changes'],
+ ['https://test.com', 'https://test.com', 'changes'],
+ ['{{ site.url }}#test', '/?{token}&_version=changes#test', 'changes'],
+ [false, null, 'changes'],
+ [null, null, 'changes', false],
+ ];
+ }
+
+ /**
+ * @covers ::previewTokenFromUrl
+ * @covers ::url
+ * @covers ::urlFromOption
+ * @covers ::urlWithQueryParams
+ * @dataProvider siteUrlProvider
+ */
+ public function testUrlSiteCustom(
+ $input,
+ $expected,
+ string $versionId,
+ bool $authenticated = true
+ ): void {
+ $this->setUpSingleLanguage();
+
+ $options = [];
+
+ if ($input !== null) {
+ $options = [
+ 'preview' => $input
+ ];
+ }
+
+ $app = $this->app->clone([
+ 'users' => [
+ [
+ 'id' => 'test',
+ 'email' => 'test@getkirby.com',
+ 'role' => 'editor'
+ ]
+ ],
+ 'roles' => [
+ [
+ 'id' => 'editor',
+ 'name' => 'editor',
+ ]
+ ],
+ 'site' => [
+ 'blueprint' => [
+ 'name' => 'site',
+ 'options' => $options
+ ]
+ ]
+ ]);
+
+ // authenticate
+ if ($authenticated === true) {
+ $app->impersonate('test@getkirby.com');
+ }
+
+ $site = $app->site();
+
+ if ($expected !== null) {
+ $expectedToken = substr(
+ hash_hmac(
+ 'sha1',
+ '{"uri":"","versionId":"' . $versionId . '"}',
+ $site->kirby()->root('content')
+ ),
+ 0,
+ 10
+ );
+ $expected = str_replace(
+ '{token}',
+ '_token=' . $expectedToken,
+ $expected
+ );
+ }
+
+ $version = new Version(
+ model: $site,
+ id: VersionId::from($versionId)
+ );
+
+ $this->assertSame($expected, $version->url());
+ }
+
+ /**
+ * @covers ::url
+ */
+ public function testUrlInvalidModel()
+ {
+ $this->expectException(LogicException::class);
+ $this->expectExceptionMessage('Only pages and the site have a content preview URL');
+
+ $this->setUpSingleLanguage();
+
+ $version = new Version(
+ model: $this->model->file(),
+ id: VersionId::latest()
+ );
+
+ $version->url();
+ }
}
diff --git a/tests/Data/DataTest.php b/tests/Data/DataTest.php
index 425b651a23..130dc50ca7 100644
--- a/tests/Data/DataTest.php
+++ b/tests/Data/DataTest.php
@@ -132,6 +132,16 @@ public function testDecodeInvalid3($handler)
Data::decode(true, $handler);
}
+ /**
+ * @covers ::decode
+ * @dataProvider handlerProvider
+ */
+ public function testDecodeInvalidNoExceptions($handler)
+ {
+ $data = Data::decode(1, $handler, fail: false);
+ $this->assertSame([], $data);
+ }
+
public static function handlerProvider(): array
{
// the PHP handler doesn't support decoding and therefore cannot be
@@ -186,6 +196,15 @@ public function testReadInvalid()
Data::read(static::TMP . '/data.foo');
}
+ /**
+ * @covers ::read
+ */
+ public function testReadInvalidNoException()
+ {
+ $data = Data::read(static::TMP . '/data.foo', fail: false);
+ $this->assertSame([], $data);
+ }
+
/**
* @covers ::write
* @covers ::handler
diff --git a/tests/Data/JsonTest.php b/tests/Data/JsonTest.php
index 8f943845f9..0764e39365 100644
--- a/tests/Data/JsonTest.php
+++ b/tests/Data/JsonTest.php
@@ -57,15 +57,6 @@ public function testDecodeInvalid2()
Json::decode(1);
}
- /**
- * @covers ::encode
- */
- public function testEncodeUnicode()
- {
- $string = 'здравей';
- $this->assertSame('"' . $string . '"', Json::encode($string));
- }
-
/**
* @covers ::decode
*/
@@ -87,4 +78,34 @@ public function testDecodeCorrupted2()
Json::decode('true');
}
+
+ /**
+ * @covers ::encode
+ */
+ public function testEncodePretty()
+ {
+ $array = [
+ 'name' => 'Homer',
+ 'children' => ['Lisa', 'Bart', 'Maggie']
+ ];
+
+ $data = Json::encode($array, pretty: true);
+ $this->assertSame('{
+ "name": "Homer",
+ "children": [
+ "Lisa",
+ "Bart",
+ "Maggie"
+ ]
+}', $data);
+ }
+
+ /**
+ * @covers ::encode
+ */
+ public function testEncodeUnicode()
+ {
+ $string = 'здравей';
+ $this->assertSame('"' . $string . '"', Json::encode($string));
+ }
}
diff --git a/tests/Form/FieldClassTest.php b/tests/Form/FieldClassTest.php
index c83ae4fff1..27b9c508b2 100644
--- a/tests/Form/FieldClassTest.php
+++ b/tests/Form/FieldClassTest.php
@@ -4,7 +4,6 @@
use Exception;
use Kirby\Cms\Page;
-use Kirby\Exception\InvalidArgumentException;
use Kirby\TestCase;
class TestField extends FieldClass
@@ -27,22 +26,6 @@ public function isSaveable(): bool
}
}
-class JsonField extends FieldClass
-{
- public function fill($value = null): void
- {
- $this->value = $this->valueFromJson($value);
- }
-}
-
-class YamlField extends FieldClass
-{
- public function fill($value = null): void
- {
- $this->value = $this->valueFromYaml($value);
- }
-}
-
class ValidatedField extends FieldClass
{
public function validations(): array
@@ -209,6 +192,7 @@ public function testDrawers()
/**
* @covers ::errors
* @covers ::validate
+ * @covers ::validations
*/
public function testErrors()
{
@@ -503,7 +487,6 @@ public function testProps()
$array = $field->toArray();
$this->assertSame($props, $field->props());
- $this->assertEquals($props + ['signature' => $array['signature']], $array); // cannot use strict assertion (array order)
}
/**
@@ -547,12 +530,14 @@ public function testSiblings()
}
/**
- * @covers ::store
+ * @covers ::toStoredValue
*/
- public function testStore()
+ public function testToStoredValue()
{
$field = new TestField();
- $this->assertSame('test', $field->store('test'));
+ $field->fill('test');
+
+ $this->assertSame('test', $field->toStoredValue());
}
/**
@@ -597,57 +582,6 @@ public function testValue()
$this->assertNull($field->value());
}
- /**
- * @covers ::valueFromJson
- */
- public function testValueFromJson()
- {
- $value = [
- [
- 'content' => 'Heading 1',
- 'id' => 'h1',
- 'type' => 'h1',
- ]
- ];
-
- // use simple value
- $field = new JsonField(['value' => json_encode($value)]);
- $this->assertSame($value, $field->value());
-
- // use empty value
- $field = new JsonField(['value' => '']);
- $this->assertSame([], $field->value());
-
- // use invalid value
- $field = new JsonField(['value' => '{invalid}']);
- $this->assertSame([], $field->value());
- }
-
- /**
- * @covers ::valueFromYaml
- */
- public function testValueFromYaml()
- {
- $value = "name: Homer\nchildren:\n - Lisa\n - Bart\n - Maggie\n";
- $expected = [
- 'name' => 'Homer',
- 'children' => ['Lisa', 'Bart', 'Maggie']
- ];
-
- // use simple value
- $field = new YamlField(['value' => $value]);
- $this->assertSame($expected, $field->value());
-
- // use empty value
- $field = new YamlField(['value' => '']);
- $this->assertSame([], $field->value());
-
- // use invalid value
- $this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('Invalid YAML data; please pass a string');
- new YamlField(['value' => new \stdClass()]);
- }
-
/**
* @covers ::when
*/
diff --git a/tests/Form/FieldTest.php b/tests/Form/FieldTest.php
index 1d04ed0804..119a58a4c9 100644
--- a/tests/Form/FieldTest.php
+++ b/tests/Form/FieldTest.php
@@ -35,16 +35,18 @@ public function tearDown(): void
Field::$mixins = $this->originalMixins;
}
- public function testWithoutModel()
+ /**
+ * @covers ::__construct
+ */
+ public function testConstructInvalidType(): void
{
- Field::$types = [
- 'test' => []
- ];
-
$this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('Field requires a model');
+ $this->expectExceptionMessage('Field "foo": The field type "test" does not exist');
- $field = new Field('test');
+ new Field('test', [
+ 'name' => 'foo',
+ 'type' => 'foo'
+ ]);
}
public function testAfter()
@@ -86,6 +88,48 @@ public function testAfter()
$this->assertSame('blog', $field->after);
}
+ /**
+ * @covers ::api
+ * @covers ::routes
+ */
+ public function testApi()
+ {
+ // no defined as default
+ Field::$types = [
+ 'test' => []
+ ];
+
+ $model = new Page(['slug' => 'test']);
+
+ $field = new Field('test', [
+ 'model' => $model,
+ ]);
+
+ $this->assertSame([], $field->api());
+
+ $routes = [
+ [
+ 'pattern' => '/',
+ 'action' => fn () => 'Hello World'
+ ]
+ ];
+
+ // return simple string
+ Field::$types = [
+ 'test' => [
+ 'api' => fn () => $routes
+ ]
+ ];
+
+ $model = new Page(['slug' => 'test']);
+
+ $field = new Field('test', [
+ 'model' => $model,
+ ]);
+
+ $this->assertSame($routes, $field->api());
+ }
+
public function testAutofocus()
{
Field::$types = [
@@ -246,32 +290,51 @@ public function testDialogs()
$this->assertSame($routes, $field->dialogs());
}
- public function testDisabled()
+ /**
+ * @covers ::drawers
+ */
+ public function testDrawers()
{
+ // no defined as default
Field::$types = [
'test' => []
];
- $page = new Page(['slug' => 'test']);
-
- // default state
+ $model = new Page(['slug' => 'test']);
$field = new Field('test', [
- 'model' => $page
+ 'model' => $model,
]);
- $this->assertFalse($field->disabled());
- $this->assertFalse($field->disabled);
+ $this->assertSame([], $field->drawers());
+
+ // test drawers
+ $routes = [
+ [
+ 'pattern' => 'foo',
+ 'load' => function () {
+ },
+ 'submit' => function () {
+ }
+ ]
+ ];
+
+ // return routes
+ Field::$types = [
+ 'test' => [
+ 'drawers' => fn () => $routes
+ ]
+ ];
- // disabled
$field = new Field('test', [
- 'model' => $page,
- 'disabled' => true
+ 'model' => $model,
]);
- $this->assertTrue($field->disabled());
- $this->assertTrue($field->disabled);
+ $this->assertSame($routes, $field->drawers());
}
+ /**
+ * @covers ::errors
+ */
public function testErrors()
{
Field::$types = [
@@ -300,6 +363,37 @@ public function testErrors()
$this->assertSame($expected, $field->errors());
}
+ /**
+ * @covers ::fill
+ */
+ public function testFill()
+ {
+ Field::$types = [
+ 'test' => [
+ 'computed' => [
+ 'computedValue' => fn () => $this->value . ' computed'
+ ]
+ ]
+ ];
+
+ $page = new Page(['slug' => 'test']);
+
+ $field = new Field('test', [
+ 'model' => $page,
+ 'value' => 'test'
+ ]);
+
+ $this->assertSame('test', $field->value());
+ $this->assertSame('test', $field->value);
+ $this->assertSame('test computed', $field->computedValue());
+
+ $field->fill('test2');
+
+ $this->assertSame('test2', $field->value());
+ $this->assertSame('test2', $field->value);
+ $this->assertSame('test2 computed', $field->computedValue());
+ }
+
public function testHelp()
{
Field::$types = [
@@ -384,6 +478,39 @@ public static function emptyValuesProvider(): array
}
/**
+ * @covers ::isDisabled
+ */
+ public function testDisabled()
+ {
+ Field::$types = [
+ 'test' => []
+ ];
+
+ $page = new Page(['slug' => 'test']);
+
+ // default state
+ $field = new Field('test', [
+ 'model' => $page
+ ]);
+
+ $this->assertFalse($field->disabled());
+ $this->assertFalse($field->disabled);
+ $this->assertFalse($field->isDisabled());
+
+ // disabled
+ $field = new Field('test', [
+ 'model' => $page,
+ 'disabled' => true
+ ]);
+
+ $this->assertTrue($field->disabled());
+ $this->assertTrue($field->disabled);
+ $this->assertTrue($field->isDisabled());
+ }
+
+ /**
+ * @covers ::isEmpty
+ * @covers ::isEmptyValue
* @dataProvider emptyValuesProvider
*/
public function testIsEmpty($value, $expected)
@@ -400,27 +527,45 @@ public function testIsEmpty($value, $expected)
]);
$this->assertSame($expected, $field->isEmpty());
- $this->assertSame($expected, $field->isEmpty($value));
+ $this->assertSame($expected, $field->isEmptyValue($value));
}
- public function testIsEmptyWithCustomFunction()
+ /**
+ * @covers ::isHidden
+ */
+ public function testIsHidden()
{
+ // default
Field::$types = [
- 'test' => [
- 'isEmpty' => fn ($value) => $value === 0
- ]
+ 'test' => []
];
$page = new Page(['slug' => 'test']);
$field = new Field('test', [
- 'model' => $page
+ 'model' => $page,
+ ]);
+
+ $this->assertFalse($field->isHidden());
+
+ // hidden
+ Field::$types = [
+ 'test' => [
+ 'hidden' => true
+ ]
+ ];
+
+ $field = new Field('test', [
+ 'model' => $page,
]);
- $this->assertFalse($field->isEmpty(null));
- $this->assertTrue($field->isEmpty(0));
+ $this->assertTrue($field->isHidden());
}
+ /**
+ * @covers ::isInvalid
+ * @covers ::isValid
+ */
public function testIsInvalidOrValid()
{
Field::$types = [
@@ -447,6 +592,9 @@ public function testIsInvalidOrValid()
$this->assertTrue($field->isInvalid());
}
+ /**
+ * @covers ::isRequired
+ */
public function testIsRequired()
{
Field::$types = [
@@ -469,6 +617,41 @@ public function testIsRequired()
$this->assertTrue($field->isRequired());
}
+ /**
+ * @covers ::isSaveable
+ * @covers ::save
+ */
+ public function testIsSaveable()
+ {
+ Field::$types = [
+ 'store-me' => [
+ 'save' => true
+ ],
+ 'dont-store-me' => [
+ 'save' => false
+ ]
+ ];
+
+ $page = new Page(['slug' => 'test']);
+
+ $a = new Field('store-me', [
+ 'model' => $page
+ ]);
+
+ $this->assertTrue($a->isSaveable());
+ $this->assertTrue($a->save());
+
+ $b = new Field('dont-store-me', [
+ 'model' => $page
+ ]);
+
+ $this->assertFalse($b->isSaveable());
+ $this->assertFalse($b->save());
+ }
+
+ /**
+ * @covers ::kirby
+ */
public function testKirby()
{
Field::$types = [
@@ -564,6 +747,9 @@ public function testMixinMin()
$this->assertSame(5, $field->min());
}
+ /**
+ * @covers ::model
+ */
public function testModel()
{
Field::$types = [
@@ -599,92 +785,233 @@ public function testName()
$this->assertSame('mytest', $field->name());
}
- public function testPlaceholder()
+ /**
+ * @covers ::needsValue
+ * @covers ::errors
+ */
+ public function testNeedsValue()
{
+ $page = new Page(['slug' => 'test']);
+
Field::$types = [
- 'test' => []
+ 'foo' => [],
+ 'bar' => [],
+ 'baz' => [],
];
- $page = new Page(['slug' => 'blog']);
+ $fields = new Fields([
+ 'foo' => [
+ 'type' => 'foo',
+ 'model' => $page,
+ 'value' => 'a'
+ ],
+ 'bar' => [
+ 'type' => 'bar',
+ 'model' => $page,
+ 'value' => 'b'
+ ],
+ 'baz' => [
+ 'type' => 'baz',
+ 'model' => $page,
+ 'value' => 'c'
+ ]
+ ]);
- // untranslated
- $field = new Field('test', [
- 'model' => $page,
- 'placeholder' => 'test'
+ // default
+ $field = new Field('foo', [
+ 'model' => $page,
]);
- $this->assertSame('test', $field->placeholder());
- $this->assertSame('test', $field->placeholder);
+ $this->assertSame([], $field->errors());
- // translated
- $field = new Field('test', [
+ // passed (simple)
+ // 'bar' is required if 'foo' value is 'x'
+ $field = new Field('bar', [
'model' => $page,
- 'placeholder' => [
- 'en' => 'en',
- 'de' => 'de'
+ 'required' => true,
+ 'when' => [
+ 'foo' => 'x'
]
- ]);
+ ], $fields);
- $this->assertSame('en', $field->placeholder());
- $this->assertSame('en', $field->placeholder);
+ $this->assertSame([], $field->errors());
- // with query
- $field = new Field('test', [
+ // passed (multiple conditions without any match)
+ // 'baz' is required if 'foo' value is 'x' and 'bar' value is 'y'
+ $field = new Field('baz', [
'model' => $page,
- 'placeholder' => '{{ page.slug }}'
- ]);
+ 'required' => true,
+ 'when' => [
+ 'foo' => 'x',
+ 'bar' => 'y'
+ ]
+ ], $fields);
- $this->assertSame('blog', $field->placeholder());
- $this->assertSame('blog', $field->placeholder);
- }
+ $this->assertSame([], $field->errors());
- public function testSave()
- {
- Field::$types = [
- 'store-me' => [
- 'save' => true
- ],
- 'dont-store-me' => [
- 'save' => false
+ // passed (multiple conditions with single match)
+ // 'baz' is required if 'foo' value is 'a' and 'bar' value is 'y'
+ $field = new Field('baz', [
+ 'model' => $page,
+ 'required' => true,
+ 'when' => [
+ 'foo' => 'a',
+ 'bar' => 'y'
+ ]
+ ], $fields);
+
+ $this->assertSame([], $field->errors());
+
+ // failed (simple)
+ // 'bar' is required if 'foo' value is 'a'
+ $field = new Field('bar', [
+ 'model' => $page,
+ 'required' => true,
+ 'when' => [
+ 'foo' => 'a'
]
+ ], $fields);
+
+ $expected = [
+ 'required' => 'Please enter something',
];
- $page = new Page(['slug' => 'test']);
+ $this->assertSame($expected, $field->errors());
- $a = new Field('store-me', [
- 'model' => $page
+ // failed (multiple conditions)
+ // 'baz' is required if 'foo' value is 'a' and 'bar' value is 'b'
+ $field = new Field('baz', [
+ 'model' => $page,
+ 'required' => true,
+ 'when' => [
+ 'foo' => 'a',
+ 'bar' => 'b'
+ ]
+ ], $fields);
+
+ $this->assertSame($expected, $field->errors());
+ }
+
+ public function testPlaceholder()
+ {
+ Field::$types = [
+ 'test' => []
+ ];
+
+ $page = new Page(['slug' => 'blog']);
+
+ // untranslated
+ $field = new Field('test', [
+ 'model' => $page,
+ 'placeholder' => 'test'
]);
- $this->assertTrue($a->save());
+ $this->assertSame('test', $field->placeholder());
+ $this->assertSame('test', $field->placeholder);
- $b = new Field('dont-store-me', [
- 'model' => $page
+ // translated
+ $field = new Field('test', [
+ 'model' => $page,
+ 'placeholder' => [
+ 'en' => 'en',
+ 'de' => 'de'
+ ]
]);
- $this->assertFalse($b->save());
+ $this->assertSame('en', $field->placeholder());
+ $this->assertSame('en', $field->placeholder);
+
+ // with query
+ $field = new Field('test', [
+ 'model' => $page,
+ 'placeholder' => '{{ page.slug }}'
+ ]);
+
+ $this->assertSame('blog', $field->placeholder());
+ $this->assertSame('blog', $field->placeholder);
}
- public function testSaveHandler()
+ /**
+ * @covers ::next
+ * @covers ::prev
+ * @covers ::siblingsCollection
+ */
+ public function testPrevNext()
{
Field::$types = [
- 'test' => [
- 'props' => [
- 'value' => fn ($value) => $value
- ],
- 'save' => fn ($value) => implode(', ', $value)
+ 'test' => []
+ ];
+
+ $model = new Page(['slug' => 'test']);
+
+ $siblings = new Fields([
+ [
+ 'type' => 'test',
+ 'name' => 'a'
+ ],
+ [
+ 'type' => 'test',
+ 'name' => 'b'
]
+ ], $model);
+
+ $this->assertNull($siblings->first()->prev());
+ $this->assertNull($siblings->last()->next());
+ $this->assertSame('b', $siblings->first()->next()->name());
+ $this->assertSame('a', $siblings->last()->prev()->name());
+ }
+
+ /**
+ * @covers ::siblings
+ * @covers ::formFields
+ */
+ public function testSiblings()
+ {
+ Field::$types = [
+ 'test' => []
];
- $page = new Page(['slug' => 'test']);
+ $model = new Page(['slug' => 'test']);
$field = new Field('test', [
- 'model' => $page,
- 'value' => ['a', 'b', 'c']
+ 'model' => $model,
]);
- $this->assertSame('a, b, c', $field->data());
+ $this->assertInstanceOf(Fields::class, $field->siblings());
+ $this->assertInstanceOf(Fields::class, $field->formFields());
+ $this->assertCount(1, $field->siblings());
+ $this->assertCount(1, $field->formFields());
+ $this->assertSame($field, $field->siblings()->first());
+ $this->assertSame($field, $field->formFields()->first());
+
+ $field = new Field(
+ type: 'test',
+ attrs: [
+ 'model' => $model,
+ ],
+ siblings: new Fields([
+ new Field('test', [
+ 'model' => $model,
+ 'name' => 'a'
+ ]),
+ new Field('test', [
+ 'model' => $model,
+ 'name' => 'b'
+ ]),
+ ])
+ );
+
+ $this->assertCount(2, $field->siblings());
+ $this->assertCount(2, $field->formFields());
+ $this->assertSame('a', $field->siblings()->first()->name());
+ $this->assertSame('a', $field->formFields()->first()->name());
+ $this->assertSame('b', $field->siblings()->last()->name());
+ $this->assertSame('b', $field->formFields()->last()->name());
}
+ /**
+ * @covers ::toArray
+ */
public function testToArray()
{
Field::$types = [
@@ -710,87 +1037,93 @@ public function testToArray()
$this->assertArrayNotHasKey('model', $array);
}
- public function testValidateByAttr()
+ /**
+ * @covers ::toFormValue
+ * @covers ::value
+ */
+ public function testToFormValue()
{
- Field::$types = [
- 'test' => []
- ];
+ Field::$types['test'] = [];
- $model = new Page(['slug' => 'test']);
+ $field = new Field('test');
+ $this->assertNull($field->toFormValue());
+ $this->assertNull($field->value());
- // with simple string validation
- $field = new Field('test', [
- 'model' => $model,
- 'value' => 'https://getkirby.com',
- 'validate' => 'url'
- ]);
- $this->assertTrue($field->isValid());
+ $field = new Field('test', ['value' => 'Test']);
+ $this->assertSame('Test', $field->toFormValue());
+ $this->assertSame('Test', $field->value());
- $field = new Field('test', [
- 'model' => $model,
- 'value' => 'definitely not a URL',
- 'validate' => 'url'
- ]);
- $this->assertFalse($field->isValid());
+ $field = new Field('test', ['default' => 'Default value']);
+ $this->assertNull($field->toFormValue());
+ $this->assertNull($field->value());
- // with an array of validators
- $field = new Field('test', [
- 'model' => $model,
- 'value' => 'thisIsATest',
- 'validate' => [
- 'startsWith' => 'this',
- 'alpha'
- ]
- ]);
- $this->assertTrue($field->isValid());
+ $field = new Field('test', ['default' => 'Default value']);
+ $this->assertSame('Default value', $field->toFormValue(true));
+ $this->assertSame('Default value', $field->value(true));
- $field = new Field('test', [
- 'model' => $model,
- 'value' => 'thisIsATest',
- 'validate' => [
- 'startsWith' => 'that',
- 'alpha'
- ]
- ]);
- $this->assertFalse($field->isValid());
+ Field::$types['test'] = [
+ 'save' => false
+ ];
- $field = new Field('test', [
- 'model' => $model,
- 'value' => 'thisIsA123',
- 'validate' => [
- 'startsWith' => 'this',
- 'alpha'
- ]
- ]);
- $this->assertFalse($field->isValid());
+ $field = new Field('test', ['value' => 'Test']);
+ $this->assertNull($field->toFormValue());
+ $this->assertNull($field->value());
}
- public function testWidth()
+ /**
+ * @covers ::toStoredValue
+ * @covers ::data
+ */
+ public function testToStoredValue()
{
Field::$types = [
- 'test' => []
+ 'test' => [
+ 'props' => [
+ 'value' => fn ($value) => $value
+ ],
+ 'save' => fn ($value) => implode(', ', $value)
+ ]
];
$page = new Page(['slug' => 'test']);
- // default width
$field = new Field('test', [
'model' => $page,
+ 'value' => ['a', 'b', 'c']
]);
- $this->assertSame('1/1', $field->width());
- $this->assertSame('1/1', $field->width);
+ $this->assertSame('a, b, c', $field->toStoredValue());
+ $this->assertSame('a, b, c', $field->data());
+ }
+
+ /**
+ * @covers ::toStoredValue
+ * @covers ::data
+ */
+ public function testToStoredValueWhenUnsaveable()
+ {
+ Field::$types = [
+ 'test' => [
+ 'save' => false
+ ]
+ ];
+
+ $model = new Page(['slug' => 'test']);
- // specific width
$field = new Field('test', [
- 'model' => $page,
- 'width' => '1/2'
+ 'model' => $model,
+ 'value' => 'something'
]);
- $this->assertSame('1/2', $field->width());
- $this->assertSame('1/2', $field->width);
+ $this->assertNull($field->toStoredValue());
+ $this->assertNull($field->data());
}
+ /**
+ * @covers ::validate
+ * @covers ::validations
+ * @covers ::errors
+ */
public function testValidate()
{
Field::$types = [
@@ -841,110 +1174,73 @@ public function testValidate()
$this->assertSame($expected, $field->errors());
}
- public function testWhenRequired()
+ /**
+ * @covers ::validate
+ * @covers ::validations
+ * @covers ::isValid
+ */
+ public function testValidateByAttr()
{
- $page = new Page(['slug' => 'test']);
-
Field::$types = [
- 'foo' => [],
- 'bar' => [],
- 'baz' => [],
+ 'test' => []
];
- $fields = new Fields([
- 'foo' => [
- 'type' => 'foo',
- 'model' => $page,
- 'value' => 'a'
- ],
- 'bar' => [
- 'type' => 'bar',
- 'model' => $page,
- 'value' => 'b'
- ],
- 'baz' => [
- 'type' => 'baz',
- 'model' => $page,
- 'value' => 'c'
- ]
- ]);
+ $model = new Page(['slug' => 'test']);
- // default
- $field = new Field('foo', [
- 'model' => $page,
+ // with simple string validation
+ $field = new Field('test', [
+ 'model' => $model,
+ 'value' => 'https://getkirby.com',
+ 'validate' => 'url'
]);
+ $this->assertTrue($field->isValid());
- $this->assertSame([], $field->errors());
-
- // passed (simple)
- // 'bar' is required if 'foo' value is 'x'
- $field = new Field('bar', [
- 'model' => $page,
- 'required' => true,
- 'when' => [
- 'foo' => 'x'
- ]
- ], $fields);
-
- $this->assertSame([], $field->errors());
-
- // passed (multiple conditions without any match)
- // 'baz' is required if 'foo' value is 'x' and 'bar' value is 'y'
- $field = new Field('baz', [
- 'model' => $page,
- 'required' => true,
- 'when' => [
- 'foo' => 'x',
- 'bar' => 'y'
- ]
- ], $fields);
-
- $this->assertSame([], $field->errors());
+ $field = new Field('test', [
+ 'model' => $model,
+ 'value' => 'definitely not a URL',
+ 'validate' => 'url'
+ ]);
+ $this->assertFalse($field->isValid());
- // passed (multiple conditions with single match)
- // 'baz' is required if 'foo' value is 'a' and 'bar' value is 'y'
- $field = new Field('baz', [
- 'model' => $page,
- 'required' => true,
- 'when' => [
- 'foo' => 'a',
- 'bar' => 'y'
+ // with an array of validators
+ $field = new Field('test', [
+ 'model' => $model,
+ 'value' => 'thisIsATest',
+ 'validate' => [
+ 'startsWith' => 'this',
+ 'alpha'
]
- ], $fields);
-
- $this->assertSame([], $field->errors());
+ ]);
+ $this->assertTrue($field->isValid());
- // failed (simple)
- // 'bar' is required if 'foo' value is 'a'
- $field = new Field('bar', [
- 'model' => $page,
- 'required' => true,
- 'when' => [
- 'foo' => 'a'
+ $field = new Field('test', [
+ 'model' => $model,
+ 'value' => 'thisIsATest',
+ 'validate' => [
+ 'startsWith' => 'that',
+ 'alpha'
]
- ], $fields);
-
- $expected = [
- 'required' => 'Please enter something',
- ];
-
- $this->assertSame($expected, $field->errors());
+ ]);
+ $this->assertFalse($field->isValid());
- // failed (multiple conditions)
- // 'baz' is required if 'foo' value is 'a' and 'bar' value is 'b'
- $field = new Field('baz', [
- 'model' => $page,
- 'required' => true,
- 'when' => [
- 'foo' => 'a',
- 'bar' => 'b'
+ $field = new Field('test', [
+ 'model' => $model,
+ 'value' => 'thisIsA123',
+ 'validate' => [
+ 'startsWith' => 'this',
+ 'alpha'
]
- ], $fields);
-
- $this->assertSame($expected, $field->errors());
+ ]);
+ $this->assertFalse($field->isValid());
}
- public function testCustomValidations()
+ /**
+ * @covers ::validate
+ * @covers ::validations
+ * @covers ::errors
+ * @covers ::isValid
+ */
+ public function testValidateWithCustomValidator()
{
Field::$types = [
'test' => [
@@ -969,52 +1265,29 @@ public function testCustomValidations()
$this->assertSame(['test' => 'Invalid value: abc'], $field->errors());
}
- public function testApi()
+ public function testWidth()
{
- // no defined as default
Field::$types = [
'test' => []
];
- $model = new Page(['slug' => 'test']);
-
- $field = new Field('test', [
- 'model' => $model,
- ]);
-
- $this->assertNull($field->api());
-
- // return simple string
- Field::$types = [
- 'test' => [
- 'api' => fn () => 'Hello World'
- ]
- ];
-
- $model = new Page(['slug' => 'test']);
+ $page = new Page(['slug' => 'test']);
+ // default width
$field = new Field('test', [
- 'model' => $model,
+ 'model' => $page,
]);
- $this->assertSame('Hello World', $field->api());
- }
-
- public function testUnsaveable()
- {
- Field::$types = [
- 'test' => [
- 'save' => false
- ]
- ];
-
- $model = new Page(['slug' => 'test']);
+ $this->assertSame('1/1', $field->width());
+ $this->assertSame('1/1', $field->width);
+ // specific width
$field = new Field('test', [
- 'model' => $model,
- 'value' => 'something'
+ 'model' => $page,
+ 'width' => '1/2'
]);
- $this->assertNull($field->data());
+ $this->assertSame('1/2', $field->width());
+ $this->assertSame('1/2', $field->width);
}
}
diff --git a/tests/Form/Fields/BlocksFieldTest.php b/tests/Form/Fields/BlocksFieldTest.php
index c4a6b69c78..9e7275079b 100644
--- a/tests/Form/Fields/BlocksFieldTest.php
+++ b/tests/Form/Fields/BlocksFieldTest.php
@@ -114,12 +114,13 @@ public function testPretty()
$expected = [
[
- 'id' => 'uuid',
- 'type' => 'heading',
'content' => [
'level' => '',
'text' => 'A nice block/heäding'
- ]
+ ],
+ 'id' => 'uuid',
+ 'isHidden' => false,
+ 'type' => 'heading',
],
];
@@ -131,7 +132,7 @@ public function testPretty()
$pretty = json_encode($expected, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$this->assertTrue($field->pretty());
- $this->assertSame($pretty, $field->store($value));
+ $this->assertSame($pretty, $field->toStoredValue());
}
public function testProps()
@@ -279,7 +280,7 @@ public function testRouteFieldset()
$this->assertSame('text', $response['type']);
}
- public function testStore()
+ public function testToStoredValue()
{
$value = [
[
@@ -293,12 +294,13 @@ public function testStore()
$expected = [
[
- 'id' => 'uuid',
- 'type' => 'heading',
'content' => [
'level' => '',
'text' => 'A nice block/heäding'
- ]
+ ],
+ 'id' => 'uuid',
+ 'isHidden' => false,
+ 'type' => 'heading',
],
];
@@ -308,12 +310,15 @@ public function testStore()
$this->assertSame(
json_encode($expected, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
- $field->store($value)
+ $field->toStoredValue()
);
// empty tests
- $this->assertSame('', $field->store(null));
- $this->assertSame('', $field->store([]));
+ $field->fill(null);
+ $this->assertSame('', $field->toStoredValue());
+
+ $field->fill([]);
+ $this->assertSame('', $field->toStoredValue());
}
public function testTranslateField()
diff --git a/tests/Form/Fields/LayoutFieldTest.php b/tests/Form/Fields/LayoutFieldTest.php
index c287d09b89..9e0f1199ff 100644
--- a/tests/Form/Fields/LayoutFieldTest.php
+++ b/tests/Form/Fields/LayoutFieldTest.php
@@ -129,7 +129,7 @@ public function testRoutes()
$this->assertCount(7, $routes);
}
- public function testStore()
+ public function testToStoredValue()
{
$value = [
[
@@ -162,7 +162,7 @@ public function testStore()
'value' => $value
]);
- $store = $field->store($value);
+ $store = $field->toStoredValue();
$this->assertIsString($store);
// ensure that the Unicode characters and slashes are not encoded
@@ -178,8 +178,11 @@ public function testStore()
$this->assertSame('A nice block/heäding', $result[0]['columns'][0]['blocks'][0]['content']['text']);
// empty tests
- $this->assertSame('', $field->store(null));
- $this->assertSame('', $field->store([]));
+ $field->fill(null);
+ $this->assertSame('', $field->toStoredValue());
+
+ $field->fill([]);
+ $this->assertSame('', $field->toStoredValue());
}
public function testValidations()
diff --git a/tests/Form/Fields/StructureFieldTest.php b/tests/Form/Fields/StructureFieldTest.php
index 6bb5042a53..85f5c28168 100644
--- a/tests/Form/Fields/StructureFieldTest.php
+++ b/tests/Form/Fields/StructureFieldTest.php
@@ -3,6 +3,7 @@
namespace Kirby\Form\Fields;
use Kirby\Cms\App;
+use Kirby\Cms\Page;
use Kirby\Form\Field;
class StructureFieldTest extends TestCase
@@ -196,9 +197,13 @@ public function testMax()
public function testNestedStructures()
{
+ $model = new Page([
+ 'slug' => 'test'
+ ]);
+
$field = $this->field('structure', [
- 'model' => 'test',
'name' => 'mothers',
+ 'model' => $model,
'fields' => [
'name' => [
'type' => 'text',
@@ -259,7 +264,7 @@ public function testNestedStructures()
$childrenField = $motherForm->fields()->children();
$this->assertSame('structure', $childrenField->type());
- $this->assertSame('test', $childrenField->model());
+ $this->assertSame($model, $childrenField->model());
// empty children form
$childrenForm = $childrenField->form();
@@ -277,7 +282,7 @@ public function testNestedStructures()
$childrenNameField = $childrenField->form()->fields()->name();
$this->assertSame('text', $childrenNameField->type());
- $this->assertSame('test', $childrenNameField->model());
+ $this->assertSame($model, $childrenNameField->model());
$this->assertSame('', $childrenNameField->data());
}
diff --git a/tests/Form/FieldsTest.php b/tests/Form/FieldsTest.php
index 0080066dcf..2d0a2f8260 100644
--- a/tests/Form/FieldsTest.php
+++ b/tests/Form/FieldsTest.php
@@ -2,40 +2,283 @@
namespace Kirby\Form;
+use Kirby\Cms\App;
use Kirby\Cms\Page;
-use Kirby\TestCase;
+use Kirby\Cms\TestCase;
+/**
+ * @coversDefaultClass \Kirby\Form\Fields
+ */
class FieldsTest extends TestCase
{
+ protected App $app;
+ protected Page $model;
+
public function setUp(): void
{
- Field::$types = [];
+ $this->app = App::instance();
+ $this->model = new Page(['slug' => 'test']);
}
- public function tearDown(): void
+ /**
+ * @covers ::__construct
+ */
+ public function testConstruct()
{
- Field::$types = [];
+ $fields = new Fields([
+ 'a' => [
+ 'type' => 'text',
+ ],
+ 'b' => [
+ 'type' => 'text',
+ ],
+ ], $this->model);
+
+ $this->assertSame('a', $fields->first()->name());
+ $this->assertSame($this->model, $fields->first()->model());
+ $this->assertSame('b', $fields->last()->name());
+ $this->assertSame($this->model, $fields->last()->model());
}
- public function testConstruct()
+ /**
+ * @covers ::__construct
+ */
+ public function testConstructWithModel()
{
- Field::$types = [
- 'test' => []
- ];
-
- $page = new Page(['slug' => 'test']);
$fields = new Fields([
'a' => [
- 'type' => 'test',
- 'model' => $page
+ 'type' => 'text',
],
'b' => [
- 'type' => 'test',
- 'model' => $page
+ 'type' => 'text',
],
- ]);
+ ], $this->model);
$this->assertSame('a', $fields->first()->name());
+ $this->assertSame($this->model, $fields->first()->model());
$this->assertSame('b', $fields->last()->name());
+ $this->assertSame($this->model, $fields->last()->model());
+ }
+
+ /**
+ * @covers ::defaults
+ */
+ public function testDefaults()
+ {
+ $fields = new Fields([
+ 'a' => [
+ 'default' => 'a',
+ 'type' => 'text'
+ ],
+ 'b' => [
+ 'default' => 'b',
+ 'type' => 'text'
+ ],
+ ], $this->model);
+
+ $this->assertSame(['a' => 'a', 'b' => 'b'], $fields->defaults());
+ }
+
+ /**
+ * @covers ::errors
+ */
+ public function testErrors()
+ {
+ $fields = new Fields([
+ 'a' => [
+ 'label' => 'A',
+ 'type' => 'text',
+ 'required' => true
+ ],
+ 'b' => [
+ 'label' => 'B',
+ 'type' => 'text',
+ 'maxlength' => 3,
+ 'value' => 'Too long'
+ ],
+ ], $this->model);
+
+ $this->assertSame([
+ 'a' => [
+ 'label' => 'A',
+ 'message' => [
+ 'required' => 'Please enter something'
+ ]
+ ],
+ 'b' => [
+ 'label' => 'B',
+ 'message' => [
+ 'maxlength' => 'Please enter a shorter value. (max. 3 characters)'
+ ]
+ ]
+ ], $fields->errors());
+
+ $fields->fill([
+ 'a' => 'A',
+ ]);
+
+ $this->assertSame([
+ 'b' => [
+ 'label' => 'B',
+ 'message' => [
+ 'maxlength' => 'Please enter a shorter value. (max. 3 characters)'
+ ]
+ ]
+ ], $fields->errors());
+ }
+
+ /**
+ * @covers ::errors
+ */
+ public function testErrorsWithoutErrors()
+ {
+ $fields = new Fields([
+ 'a' => [
+ 'type' => 'text',
+ ],
+ 'b' => [
+ 'type' => 'text',
+ ],
+ ], $this->model);
+
+ $this->assertSame([], $fields->errors());
+ }
+
+ /**
+ * @covers ::fill
+ */
+ public function testFill()
+ {
+ $fields = new Fields([
+ 'a' => [
+ 'type' => 'text',
+ 'value' => 'A'
+ ],
+ 'b' => [
+ 'type' => 'text',
+ 'value' => 'B'
+ ],
+ ], $this->model);
+
+ $this->assertSame([
+ 'a' => 'A',
+ 'b' => 'B'
+ ], $fields->toArray(fn ($field) => $field->value()));
+
+ $fields->fill($input = [
+ 'a' => 'A updated',
+ 'b' => 'B updated'
+ ]);
+
+ $this->assertSame($input, $fields->toArray(fn ($field) => $field->value()));
+ }
+
+ /**
+ * @covers ::findByKey
+ * @covers ::findByKeyRecursive
+ */
+ public function testFind()
+ {
+ Field::$types['test'] = [
+ 'methods' => [
+ 'form' => function () {
+ return new Form([
+ 'fields' => [
+ 'child' => [
+ 'type' => 'text',
+ ],
+ ],
+ 'model' => $this->model
+ ]);
+ }
+ ]
+ ];
+
+ $fields = new Fields([
+ 'mother' => [
+ 'type' => 'test',
+ ],
+ ], $this->model);
+
+ $this->assertSame('mother', $fields->find('mother')->name());
+ $this->assertSame('child', $fields->find('mother+child')->name());
+ $this->assertNull($fields->find('mother+missing-child'));
+ }
+
+ /**
+ * @covers ::findByKey
+ * @covers ::findByKeyRecursive
+ */
+ public function testFindWhenFieldHasNoForm()
+ {
+ $fields = new Fields([
+ 'mother' => [
+ 'type' => 'text',
+ ],
+ ], $this->model);
+
+ $this->assertNull($fields->find('mother+child'));
+ }
+
+ /**
+ * @covers ::toArray
+ */
+ public function testToArray()
+ {
+ $fields = new Fields([
+ 'a' => [
+ 'type' => 'text',
+ ],
+ 'b' => [
+ 'type' => 'text',
+ ],
+ ], $this->model);
+
+ $this->assertSame(['a' => 'a', 'b' => 'b'], $fields->toArray(fn ($field) => $field->name()));
+ }
+
+ /**
+ * @covers ::toFormValues
+ */
+ public function testToFormValues()
+ {
+ $fields = new Fields([
+ 'a' => [
+ 'type' => 'text',
+ 'value' => 'Value a'
+ ],
+ 'b' => [
+ 'type' => 'text',
+ 'value' => 'Value b'
+ ],
+ ], $this->model);
+
+ $this->assertSame(['a' => 'Value a', 'b' => 'Value b'], $fields->toFormValues());
+ }
+
+ /**
+ * @covers ::toStoredValues
+ */
+ public function testToStoredValues()
+ {
+ Field::$types['test'] = [
+ 'save' => function ($value) {
+ return $value . ' stored';
+ }
+ ];
+
+ $fields = new Fields([
+ 'a' => [
+ 'type' => 'test',
+ 'value' => 'Value a'
+ ],
+ 'b' => [
+ 'type' => 'test',
+ 'value' => 'Value b'
+ ],
+ ], $this->model);
+
+ $this->assertSame(['a' => 'Value a', 'b' => 'Value b'], $fields->toFormValues());
+ $this->assertSame(['a' => 'Value a stored', 'b' => 'Value b stored'], $fields->toStoredValues());
}
}
diff --git a/tests/Form/FormTest.php b/tests/Form/FormTest.php
index ae4464df6a..fbe03ae956 100644
--- a/tests/Form/FormTest.php
+++ b/tests/Form/FormTest.php
@@ -5,6 +5,7 @@
use Exception;
use Kirby\Cms\App;
use Kirby\Cms\File;
+use Kirby\Cms\ModelWithContent;
use Kirby\Cms\Page;
use Kirby\TestCase;
@@ -13,12 +14,34 @@
*/
class FormTest extends TestCase
{
+ public const TMP = KIRBY_TMP_DIR . '/Form.Form';
+
+ protected ModelWithContent $model;
+
+ public function setUp(): void
+ {
+ $this->setUpSingleLanguage([
+ 'children' => [
+ [
+ 'slug' => 'test'
+ ]
+ ]
+ ]);
+
+ $this->model = $this->app->page('test');
+ $this->setUpTmp();
+ }
+
public function tearDown(): void
{
App::destroy();
+ $this->tearDownTmp();
}
- public function testDataWithoutFields()
+ /**
+ * @covers ::content
+ */
+ public function testContent()
{
$form = new Form([
'fields' => [],
@@ -28,35 +51,37 @@ public function testDataWithoutFields()
]
]);
- $this->assertSame($values, $form->data());
+ $this->assertSame($values, $form->content());
}
/**
- * @covers ::exceptionField
+ * @covers ::content
+ * @covers ::data
*/
- public function testExceptionFieldDebug()
+ public function testContentAndDataFromUnsaveableFields()
{
- $exception = new Exception('This is an error');
-
- $app = new App();
- $props = ['name' => 'test', 'model' => $app->site()];
- $field = Form::exceptionField($exception, $props)->toArray();
- $this->assertSame('info', $field['type']);
- $this->assertSame('Error in "test" field.', $field['label']);
- $this->assertSame('This is an error
', $field['text']);
- $this->assertSame('negative', $field['theme']);
+ $form = new Form([
+ 'fields' => [
+ 'info' => [
+ 'type' => 'info',
+ ]
+ ],
+ 'model' => $this->model,
+ 'values' => [
+ 'info' => 'Yay'
+ ]
+ ]);
- $app = $app->clone(['options' => ['debug' => true]]);
- $props = ['name' => 'test', 'model' => $app->site()];
- $field = Form::exceptionField($exception, $props)->toArray();
- $this->assertSame('info', $field['type']);
- $this->assertSame('Error in "test" field.', $field['label']);
- $this->assertStringContainsString('This is an error in file:', $field['text']);
- $this->assertStringContainsString('tests/Form/FormTest.php line: 39
', $field['text']);
- $this->assertSame('negative', $field['theme']);
+ $this->assertCount(0, $form->content());
+ $this->assertArrayNotHasKey('info', $form->content());
+ $this->assertCount(1, $form->data());
+ $this->assertArrayHasKey('info', $form->data());
}
- public function testValuesWithoutFields()
+ /**
+ * @covers ::data
+ */
+ public function testDataWithoutFields()
{
$form = new Form([
'fields' => [],
@@ -66,25 +91,21 @@ public function testValuesWithoutFields()
]
]);
- $this->assertSame($values, $form->values());
+ $this->assertSame($values, $form->data());
}
+ /**
+ * @covers ::data
+ */
public function testDataFromUnsaveableFields()
{
- new App([
- 'roots' => [
- 'index' => '/dev/null'
- ]
- ]);
-
- $page = new Page(['slug' => 'test']);
$form = new Form([
'fields' => [
'info' => [
'type' => 'info',
- 'model' => $page
]
],
+ 'model' => $this->model,
'values' => [
'info' => 'Yay'
]
@@ -93,28 +114,23 @@ public function testDataFromUnsaveableFields()
$this->assertNull($form->data()['info']);
}
+ /**
+ * @covers ::data
+ */
public function testDataFromNestedFields()
{
- new App([
- 'roots' => [
- 'index' => '/dev/null'
- ]
- ]);
-
- $page = new Page(['slug' => 'test']);
$form = new Form([
'fields' => [
'structure' => [
'type' => 'structure',
- 'model' => $page,
'fields' => [
'tags' => [
'type' => 'tags',
- 'model' => $page
]
]
]
],
+ 'model' => $this->model,
'values' => $values = [
'structure' => [
[
@@ -127,129 +143,146 @@ public function testDataFromNestedFields()
$this->assertSame('a, b', $form->data()['structure'][0]['tags']);
}
- public function testInvalidFieldType()
- {
- new App([
- 'roots' => [
- 'index' => '/dev/null'
- ]
- ]);
-
- $page = new Page(['slug' => 'test']);
- $form = new Form([
- 'fields' => [
- 'test' => [
- 'type' => 'does-not-exist',
- 'model' => $page
- ]
- ]
- ]);
-
- $field = $form->fields()->first();
-
- $this->assertSame('info', $field->type());
- $this->assertSame('negative', $field->theme());
- $this->assertSame('Error in "test" field.', $field->label());
- $this->assertSame('Field "test": The field type "does-not-exist" does not exist
', $field->text());
- }
-
- public function testFieldOrder()
+ /**
+ * @covers ::data
+ * @covers ::values
+ */
+ public function testDataWithCorrectFieldOrder()
{
- new App([
- 'roots' => [
- 'index' => '/dev/null'
- ]
- ]);
-
- $page = new Page(['slug' => 'test']);
$form = new Form([
'fields' => [
'a' => [
- 'type' => 'text',
- 'model' => $page
+ 'type' => 'text',
],
'b' => [
- 'type' => 'text',
- 'model' => $page
+ 'type' => 'text',
]
],
+ 'input' => [
+ 'b' => 'B modified'
+ ],
+ 'model' => $this->model,
'values' => [
'c' => 'C',
'b' => 'B',
'a' => 'A',
],
- 'input' => [
- 'b' => 'B modified'
- ]
]);
- $this->assertTrue(['a' => 'A', 'b' => 'B modified', 'c' => 'C'] === $form->values());
$this->assertTrue(['a' => 'A', 'b' => 'B modified', 'c' => 'C'] === $form->data());
+ $this->assertTrue(['a' => 'A', 'b' => 'B modified', 'c' => 'C'] === $form->values());
}
- public function testStrictMode()
+ /**
+ * @covers ::data
+ * @covers ::values
+ */
+ public function testDataWithStrictMode()
{
- new App([
- 'roots' => [
- 'index' => '/dev/null'
- ]
- ]);
-
- $page = new Page(['slug' => 'test']);
$form = new Form([
'fields' => [
'a' => [
'type' => 'text',
- 'model' => $page
],
'b' => [
'type' => 'text',
- 'model' => $page
]
],
+ 'input' => [
+ 'c' => 'C'
+ ],
+ 'model' => $this->model,
+ 'strict' => true,
'values' => [
'b' => 'B',
'a' => 'A'
],
- 'input' => [
- 'c' => 'C'
- ],
- 'strict' => true
]);
- $this->assertTrue(['a' => 'A', 'b' => 'B'] === $form->values());
$this->assertTrue(['a' => 'A', 'b' => 'B'] === $form->data());
+ $this->assertTrue(['a' => 'A', 'b' => 'B'] === $form->values());
}
- public function testErrors()
+ /**
+ * @covers ::data
+ * @covers ::values
+ */
+ public function testDataWithUntranslatedFields()
{
- new App([
- 'roots' => [
- 'index' => '/dev/null'
+ $this->setUpMultiLanguage();
+
+ $this->model = new Page([
+ 'slug' => 'test',
+ 'blueprint' => [
+ 'fields' => [
+ 'a' => [
+ 'type' => 'text'
+ ],
+ 'b' => [
+ 'type' => 'text',
+ 'translate' => false
+ ]
+ ],
+ ]
+ ]);
+
+ // default language
+ $form = Form::for($this->model, [
+ 'input' => [
+ 'a' => 'A',
+ 'b' => 'B'
+ ]
+ ]);
+
+ $expected = [
+ 'a' => 'A',
+ 'b' => 'B'
+ ];
+
+ $this->assertSame($expected, $form->values());
+
+ // secondary language
+ $form = Form::for($this->model, [
+ 'language' => 'de',
+ 'input' => [
+ 'a' => 'A',
+ 'b' => 'B'
]
]);
- $page = new Page(['slug' => 'test']);
+ $expected = [
+ 'a' => 'A',
+ 'b' => ''
+ ];
+
+ $this->assertSame($expected, $form->values());
+ }
+
+ /**
+ * @covers ::errors
+ * @covers ::isInvalid
+ * @covers ::isValid
+ */
+ public function testErrors()
+ {
$form = new Form([
'fields' => [
'a' => [
'label' => 'Email',
'type' => 'email',
- 'model' => $page
],
'b' => [
'label' => 'Url',
'type' => 'url',
- 'model' => $page
]
],
+ 'model' => $this->model,
'values' => [
'a' => 'A',
'b' => 'B',
]
]);
-
$this->assertTrue($form->isInvalid());
$this->assertFalse($form->isValid());
@@ -274,106 +307,86 @@ public function testErrors()
$this->assertSame($expected, $form->errors());
}
- public function testToArray()
+ /**
+ * @covers ::exceptionField
+ */
+ public function testExceptionField()
{
- new App([
- 'roots' => [
- 'index' => '/dev/null'
- ]
- ]);
-
- $page = new Page(['slug' => 'test']);
$form = new Form([
'fields' => [
- 'a' => [
- 'label' => 'A',
- 'type' => 'text',
- 'model' => $page
- ],
- 'b' => [
- 'label' => 'B',
- 'type' => 'text',
- 'model' => $page
+ 'test' => [
+ 'type' => 'does-not-exist',
+ 'model' => $this->model
]
- ],
- 'values' => [
- 'a' => 'A',
- 'b' => 'B',
]
]);
- $this->assertSame([], $form->toArray()['errors']);
- $this->assertArrayHasKey('a', $form->toArray()['fields']);
- $this->assertArrayHasKey('b', $form->toArray()['fields']);
- $this->assertCount(2, $form->toArray()['fields']);
- $this->assertFalse($form->toArray()['invalid']);
+ $field = $form->fields()->first();
+
+ $this->assertSame('info', $field->type());
+ $this->assertSame('negative', $field->theme());
+ $this->assertSame('Error in "test" field.', $field->label());
+ $this->assertSame('Field "test": The field type "does-not-exist" does not exist
', $field->text());
}
- public function testContent()
+ /**
+ * @covers ::exceptionField
+ */
+ public function testExceptionFieldInDebugMode()
{
- $form = new Form([
- 'fields' => [],
- 'values' => $values = [
- 'a' => 'A',
- 'b' => 'B'
- ]
- ]);
+ $exception = new Exception('This is an error');
- $this->assertSame($values, $form->content());
- }
+ // debug mode off
+ $props = [
+ 'name' => 'test',
+ 'model' => $this->app->site()
+ ];
- public function testContentFromUnsaveableFields()
- {
- new App([
- 'roots' => [
- 'index' => '/dev/null'
- ]
- ]);
+ $field = Form::exceptionField($exception, $props)->toArray();
+ $this->assertSame('info', $field['type']);
+ $this->assertSame('Error in "test" field.', $field['label']);
+ $this->assertSame('This is an error
', $field['text']);
+ $this->assertSame('negative', $field['theme']);
- $page = new Page(['slug' => 'test']);
- $form = new Form([
- 'fields' => [
- 'info' => [
- 'type' => 'info',
- 'model' => $page
- ]
- ],
- 'values' => [
- 'info' => 'Yay'
- ]
- ]);
+ // debug mode on
+ $this->app->clone(['options' => ['debug' => true]]);
- $this->assertCount(0, $form->content());
- $this->assertArrayNotHasKey('info', $form->content());
- $this->assertCount(1, $form->data());
- $this->assertArrayHasKey('info', $form->data());
+ $props = [
+ 'name' => 'test',
+ 'model' => $this->app->site()
+ ];
+
+ $field = Form::exceptionField($exception, $props)->toArray();
+ $this->assertSame('info', $field['type']);
+ $this->assertSame('Error in "test" field.', $field['label']);
+ $this->assertStringContainsString('This is an error in file:', $field['text']);
+ $this->assertStringContainsString('tests/Form/FormTest.php line:', $field['text']);
+ $this->assertSame('negative', $field['theme']);
}
- public function testStrings()
+ /**
+ * @covers ::for
+ */
+ public function testForFileWithoutBlueprint()
{
- $form = new Form([
- 'fields' => [],
- 'values' => [
- 'a' => 'A',
- 'b' => 'B',
- 'c' => [
- 'd' => 'D',
- 'e' => 'E'
- ]
- ]
+ $file = new File([
+ 'filename' => 'test.jpg',
+ 'parent' => $this->model,
+ 'content' => []
]);
- $this->assertSame([
- 'a' => 'A',
- 'b' => 'B',
- 'c' => "d: D\ne: E\n"
- ], $form->strings());
+ $form = Form::for($file, [
+ 'values' => ['a' => 'A', 'b' => 'B']
+ ]);
+
+ $this->assertSame(['a' => 'A', 'b' => 'B'], $form->data());
}
- public function testPageForm()
+ /**
+ * @covers ::for
+ */
+ public function testForPage()
{
- App::instance();
-
$page = new Page([
'slug' => 'test',
'content' => [
@@ -407,7 +420,10 @@ public function testPageForm()
$this->assertSame('', $values['date']);
}
- public function testPageFormWithClosures()
+ /**
+ * @covers ::for
+ */
+ public function testForPageWithClosureValues()
{
$page = new Page([
'slug' => 'test',
@@ -429,95 +445,130 @@ public function testPageFormWithClosures()
$this->assertSame('B', $values['b']);
}
- public function testFileFormWithoutBlueprint()
+ /**
+ * @covers ::strings
+ */
+ public function testStrings()
{
- new App([
- 'roots' => [
- 'index' => '/dev/null'
+ $form = new Form([
+ 'fields' => [],
+ 'values' => [
+ 'a' => 'A',
+ 'b' => 'B',
+ 'c' => [
+ 'd' => 'D',
+ 'e' => 'E'
+ ]
]
]);
- $page = new Page([
- 'slug' => 'test'
- ]);
-
- $file = new File([
- 'filename' => 'test.jpg',
- 'parent' => $page,
- 'content' => []
- ]);
-
- $form = Form::for($file, [
- 'values' => ['a' => 'A', 'b' => 'B']
- ]);
-
- $this->assertSame(['a' => 'A', 'b' => 'B'], $form->data());
+ $this->assertSame([
+ 'a' => 'A',
+ 'b' => 'B',
+ 'c' => "d: D\ne: E\n"
+ ], $form->strings());
}
- public function testUntranslatedFields()
+ /**
+ * @covers ::toArray
+ */
+ public function testToArray()
{
- $app = new App([
- 'roots' => [
- 'index' => '/dev/null'
- ],
- 'options' => [
- 'languages' => true
- ],
- 'languages' => [
- [
- 'code' => 'en',
- 'default' => true
+ $form = new Form([
+ 'fields' => [
+ 'a' => [
+ 'label' => 'A',
+ 'type' => 'text',
],
- [
- 'code' => 'de'
+ 'b' => [
+ 'label' => 'B',
+ 'type' => 'text',
]
+ ],
+ 'model' => $this->model,
+ 'values' => [
+ 'a' => 'A',
+ 'b' => 'B',
]
]);
- $page = new Page([
- 'slug' => 'test',
- 'blueprint' => [
- 'fields' => [
- 'a' => [
- 'type' => 'text'
- ],
- 'b' => [
- 'type' => 'text',
- 'translate' => false
- ]
+ $this->assertSame([], $form->toArray()['errors']);
+ $this->assertArrayHasKey('a', $form->toArray()['fields']);
+ $this->assertArrayHasKey('b', $form->toArray()['fields']);
+ $this->assertCount(2, $form->toArray()['fields']);
+ $this->assertFalse($form->toArray()['invalid']);
+ }
+
+ /**
+ * @covers ::toFormValues
+ */
+ public function testToFormValues()
+ {
+ $form = new Form([
+ 'fields' => [
+ 'a' => [
+ 'type' => 'text',
],
+ 'b' => [
+ 'type' => 'text',
+ ]
+ ],
+ 'values' => $values = [
+ 'a' => 'A',
+ 'b' => 'B',
]
]);
- // default language
- $form = Form::for($page, [
- 'input' => [
+ $this->assertSame($values, $form->toFormValues());
+ }
+
+ /**
+ * @covers ::toStoredValues
+ */
+ public function testToStoredValues()
+ {
+ Field::$types['test'] = [
+ 'save' => function ($value) {
+ return $value . ' stored';
+ }
+ ];
+
+ $form = new Form([
+ 'fields' => [
+ 'a' => [
+ 'type' => 'test',
+ ],
+ 'b' => [
+ 'type' => 'test',
+ ]
+ ],
+ 'values' => [
'a' => 'A',
- 'b' => 'B'
+ 'b' => 'B',
]
]);
$expected = [
- 'a' => 'A',
- 'b' => 'B'
+ 'a' => 'A stored',
+ 'b' => 'B stored'
];
- $this->assertSame($expected, $form->values());
+ $this->assertSame($expected, $form->toStoredValues());
+ }
- // secondary language
- $form = Form::for($page, [
- 'language' => 'de',
- 'input' => [
+ /**
+ * @covers ::values
+ */
+ public function testValuesWithoutFields()
+ {
+ $form = new Form([
+ 'fields' => [],
+ 'values' => $values = [
'a' => 'A',
'b' => 'B'
]
]);
- $expected = [
- 'a' => 'A',
- 'b' => ''
- ];
-
- $this->assertSame($expected, $form->values());
+ $this->assertSame($values, $form->values());
}
}
diff --git a/tests/Panel/Areas/FileDialogsTest.php b/tests/Panel/Areas/FileDialogsTest.php
index 84d6ded987..54bd37b9dc 100644
--- a/tests/Panel/Areas/FileDialogsTest.php
+++ b/tests/Panel/Areas/FileDialogsTest.php
@@ -105,12 +105,6 @@ public function testChangeNameForPageFileOnSubmit(): void
$dialog = $this->dialog('pages/test/files/test.jpg/changeName');
$this->assertSame('file.changeName', $dialog['event']);
- $this->assertSame([
- 'content/move' => [
- '/pages/test/files/test.jpg',
- '/pages/test/files/new-test.jpg'
- ]
- ], $dialog['dispatch']);
$this->assertSame(200, $dialog['code']);
$this->assertSame('new-test', $this->app->page('test')->file('new-test.jpg')->name());
}
@@ -154,12 +148,6 @@ public function testChangeNameForSiteFileOnSubmit(): void
$dialog = $this->dialog('site/files/test.jpg/changeName');
$this->assertSame('file.changeName', $dialog['event']);
- $this->assertSame([
- 'content/move' => [
- '/site/files/test.jpg',
- '/site/files/new-test.jpg'
- ]
- ], $dialog['dispatch']);
$this->assertSame(200, $dialog['code']);
$this->assertSame('new-test', $this->app->site()->file('new-test.jpg')->name());
}
@@ -203,12 +191,6 @@ public function testChangeNameForUserFileOnSubmit(): void
$dialog = $this->dialog('users/test/files/test.jpg/changeName');
$this->assertSame('file.changeName', $dialog['event']);
- $this->assertSame([
- 'content/move' => [
- '/users/test/files/test.jpg',
- '/users/test/files/new-test.jpg'
- ]
- ], $dialog['dispatch']);
$this->assertSame(200, $dialog['code']);
$this->assertSame('new-test', $this->app->user('test')->file('new-test.jpg')->name());
}
@@ -460,7 +442,6 @@ public function testDeletePageFileOnSubmit(): void
$dialog = $this->dialog('pages/test/files/test.jpg/delete');
$this->assertSame('file.delete', $dialog['event']);
- $this->assertSame(['content/remove' => ['/pages/test/files/test.jpg']], $dialog['dispatch']);
$this->assertSame(200, $dialog['code']);
$this->assertFalse($dialog['redirect']);
$this->assertCount(0, $this->app->page('test')->files());
@@ -499,7 +480,6 @@ public function testDeleteSiteFileOnSubmit(): void
$dialog = $this->dialog('site/files/test.jpg/delete');
$this->assertSame('file.delete', $dialog['event']);
- $this->assertSame(['content/remove' => ['/site/files/test.jpg']], $dialog['dispatch']);
$this->assertSame(200, $dialog['code']);
$this->assertFalse($dialog['redirect']);
$this->assertCount(0, $this->app->site()->files());
@@ -538,7 +518,6 @@ public function testDeleteUserFileOnSubmit(): void
$dialog = $this->dialog('users/test/files/test.jpg/delete');
$this->assertSame('file.delete', $dialog['event']);
- $this->assertSame(['content/remove' => ['/users/test/files/test.jpg']], $dialog['dispatch']);
$this->assertSame(200, $dialog['code']);
$this->assertFalse($dialog['redirect']);
$this->assertCount(0, $this->app->user('test')->files());
diff --git a/tests/Panel/Areas/PageDialogsTest.php b/tests/Panel/Areas/PageDialogsTest.php
index f5ef0abe43..abe023cc01 100644
--- a/tests/Panel/Areas/PageDialogsTest.php
+++ b/tests/Panel/Areas/PageDialogsTest.php
@@ -480,12 +480,6 @@ public function testChangeTitleOnSubmitWithSlugOnly(): void
$this->assertSame(['page.changeSlug'], $dialog['event']);
$this->assertSame(200, $dialog['code']);
- $this->assertSame([
- 'content/move' => [
- '/pages/test',
- '/pages/new-slug'
- ]
- ], $dialog['dispatch']);
$this->assertSame('new-slug', $this->app->page('new-slug')->slug());
}
@@ -529,12 +523,6 @@ public function testChangeTitleOnSubmitWithSlugAndTitle(): void
$this->assertSame(['page.changeTitle', 'page.changeSlug'], $dialog['event']);
$this->assertSame(200, $dialog['code']);
- $this->assertSame([
- 'content/move' => [
- '/pages/test',
- '/pages/new-slug'
- ]
- ], $dialog['dispatch']);
$this->assertSame('New title', $this->app->page('new-slug')->title()->value());
$this->assertSame('new-slug', $this->app->page('new-slug')->slug());
diff --git a/tests/Panel/Areas/SiteDialogsTest.php b/tests/Panel/Areas/SiteDialogsTest.php
index 97696c0f36..e495ae66ae 100644
--- a/tests/Panel/Areas/SiteDialogsTest.php
+++ b/tests/Panel/Areas/SiteDialogsTest.php
@@ -36,4 +36,15 @@ public function testChangeTitleOnSubmit(): void
$this->assertSame('Test', $this->app->site()->title()->value());
}
+
+ public function testChanges(): void
+ {
+ $dialog = $this->dialog('changes');
+ $props = $dialog['props'];
+
+ $this->assertSame('k-changes-dialog', $dialog['component']);
+ $this->assertSame([], $props['files']);
+ $this->assertSame([], $props['pages']);
+ $this->assertSame([], $props['users']);
+ }
}
diff --git a/tests/Panel/Areas/SiteTest.php b/tests/Panel/Areas/SiteTest.php
index ce6e169359..5bc528ff66 100644
--- a/tests/Panel/Areas/SiteTest.php
+++ b/tests/Panel/Areas/SiteTest.php
@@ -37,8 +37,15 @@ public function testPage(): void
$model = $props['model'];
$this->assertSame('default', $props['blueprint']);
- $this->assertSame(['state' => null, 'data' => false], $props['lock']);
-
+ $this->assertSame([
+ 'isLegacy' => false,
+ 'isLocked' => false,
+ 'modified' => null,
+ 'user' => [
+ 'id' => 'test',
+ 'email' => 'test@getkirby.com'
+ ]
+ ], $props['lock']);
$this->assertArrayNotHasKey('tab', $props);
$this->assertSame([], $props['tabs']);
@@ -88,8 +95,15 @@ public function testPageFile(): void
$model = $props['model'];
$this->assertSame('image', $props['blueprint']);
- $this->assertSame(['state' => null, 'data' => false], $props['lock']);
-
+ $this->assertSame([
+ 'isLegacy' => false,
+ 'isLocked' => false,
+ 'modified' => null,
+ 'user' => [
+ 'id' => 'test',
+ 'email' => 'test@getkirby.com'
+ ]
+ ], $props['lock']);
$this->assertArrayNotHasKey('tab', $props);
$this->assertSame([], $props['tabs']);
@@ -107,6 +121,29 @@ public function testPageFile(): void
$this->assertNull($props['prev']);
}
+ public function testPagePreview(): void
+ {
+ $this->login();
+
+ $page = $this->app->site()->createChild([
+ 'slug' => 'test',
+ 'isDraft' => false,
+ 'content' => [
+ 'title' => 'Test'
+ ]
+ ]);
+
+ $view = $this->view('pages/test/preview/changes');
+ $props = $view['props'];
+
+ $token = $page->version('changes')->previewToken();
+
+ $this->assertSame('k-preview-view', $view['component']);
+ $this->assertSame('Test | Changes', $view['title']);
+ $this->assertSame('/test?_token=' . $token . '&_version=changes', $props['src']['changes']);
+ $this->assertSame('/test', $props['src']['latest']);
+ }
+
public function testSiteWithoutAuthentication(): void
{
$this->assertRedirect('site', 'login');
@@ -156,8 +193,15 @@ public function testSiteFile(): void
$model = $props['model'];
$this->assertSame('image', $props['blueprint']);
- $this->assertSame(['state' => null, 'data' => false], $props['lock']);
-
+ $this->assertSame([
+ 'isLegacy' => false,
+ 'isLocked' => false,
+ 'modified' => null,
+ 'user' => [
+ 'id' => 'test',
+ 'email' => 'test@getkirby.com'
+ ]
+ ], $props['lock']);
$this->assertArrayNotHasKey('tab', $props);
$this->assertSame([], $props['tabs']);
@@ -175,6 +219,28 @@ public function testSiteFile(): void
$this->assertNull($props['prev']);
}
+ public function testSitePreview(): void
+ {
+ $this->login();
+
+ $site = $this->app->site();
+
+ $site->createChild([
+ 'slug' => 'home',
+ 'isDraft' => false
+ ]);
+
+ $view = $this->view('site/preview/changes');
+ $props = $view['props'];
+
+ $token = $site->version('changes')->previewToken();
+
+ $this->assertSame('k-preview-view', $view['component']);
+ $this->assertSame('Site | Changes', $view['title']);
+ $this->assertSame('/?_token=' . $token . '&_version=changes', $props['src']['changes']);
+ $this->assertSame('/', $props['src']['latest']);
+ }
+
public function testSiteTitle(): void
{
$this->app([
diff --git a/tests/Panel/Areas/UsersDialogsTest.php b/tests/Panel/Areas/UsersDialogsTest.php
index 17995a95ce..3a4e41ca1b 100644
--- a/tests/Panel/Areas/UsersDialogsTest.php
+++ b/tests/Panel/Areas/UsersDialogsTest.php
@@ -269,7 +269,6 @@ public function testDeleteOnSubmit(): void
$dialog = $this->dialog('users/editor/delete');
$this->assertSame('user.delete', $dialog['event']);
- $this->assertSame(['/users/editor'], $dialog['dispatch']['content/remove']);
$this->assertSame(200, $dialog['code']);
$this->assertFalse($dialog['redirect']);
$this->assertCount(1, $this->app->users());
diff --git a/tests/Panel/ChangesDialogTest.php b/tests/Panel/ChangesDialogTest.php
new file mode 100644
index 0000000000..05654740e7
--- /dev/null
+++ b/tests/Panel/ChangesDialogTest.php
@@ -0,0 +1,183 @@
+install();
+ $this->login();
+
+ $this->changes = new Changes();
+ }
+
+ public function setUpModels(): void
+ {
+ $this->app = $this->app->clone([
+ 'roots' => [
+ 'index' => static::TMP
+ ],
+ 'site' => [
+ 'children' => [
+ [
+ 'slug' => 'test',
+ 'content' => [
+ 'uuid' => 'test'
+ ],
+ 'files' => [
+ [
+ 'filename' => 'test.jpg',
+ 'content' => [
+ 'uuid' => 'test'
+ ],
+ ]
+ ]
+ ]
+ ]
+ ],
+ 'users' => [
+ [
+ 'id' => 'test',
+ ]
+ ]
+ ]);
+
+ $this->app->impersonate('kirby');
+
+ Uuids::populate();
+ }
+
+ /**
+ * @covers ::files
+ */
+ public function testFiles(): void
+ {
+ $this->setUpModels();
+
+ $this->app->file('file://test')->version(VersionId::latest())->save([]);
+ $this->app->file('file://test')->version(VersionId::changes())->save([]);
+
+ $dialog = new ChangesDialog();
+ $files = $dialog->files();
+
+ $this->assertCount(1, $files);
+ $this->assertSame('test.jpg', $files[0]['text']);
+ $this->assertSame('/pages/test/files/test.jpg', $files[0]['link']);
+ }
+
+ /**
+ * @covers ::files
+ */
+ public function testFilesWithoutChanges(): void
+ {
+ $dialog = new ChangesDialog();
+ $this->assertSame([], $dialog->files());
+ }
+
+ /**
+ * @covers ::items
+ */
+ public function testItems(): void
+ {
+ $this->setUpModels();
+ $page = $this->app->page('page://test');
+ $page->version(VersionId::latest())->save([]);
+ $page->version(VersionId::changes())->save([]);
+
+ $dialog = new ChangesDialog();
+ $pages = new Pages([$page]);
+ $items = $dialog->items($pages);
+
+ $this->assertCount(1, $items);
+
+ $this->assertSame('test', $items[0]['text']);
+ $this->assertSame('/pages/test', $items[0]['link']);
+ }
+
+ /**
+ * @covers ::load
+ */
+ public function testLoad(): void
+ {
+ $dialog = new ChangesDialog();
+
+ $expected = [
+ 'component' => 'k-changes-dialog',
+ 'props' => [
+ 'files' => [],
+ 'pages' => [],
+ 'users' => [],
+ ]
+ ];
+
+ $this->assertSame($expected, $dialog->load());
+ }
+
+ /**
+ * @covers ::pages
+ */
+ public function testPages(): void
+ {
+ $this->setUpModels();
+
+ $this->app->page('page://test')->version(VersionId::latest())->save([]);
+ $this->app->page('page://test')->version(VersionId::changes())->save([]);
+
+ $dialog = new ChangesDialog();
+ $pages = $dialog->pages();
+
+ $this->assertCount(1, $pages);
+ $this->assertSame('test', $pages[0]['text']);
+ $this->assertSame('/pages/test', $pages[0]['link']);
+ }
+
+ /**
+ * @covers ::pages
+ */
+ public function testPagesWithoutChanges(): void
+ {
+ $dialog = new ChangesDialog();
+ $this->assertSame([], $dialog->pages());
+ }
+
+ /**
+ * @covers ::users
+ */
+ public function testUsers(): void
+ {
+ $this->setUpModels();
+
+ $this->app->user('user://test')->version(VersionId::latest())->save([]);
+ $this->app->user('user://test')->version(VersionId::changes())->save([]);
+
+ $dialog = new ChangesDialog();
+ $users = $dialog->users();
+
+ $this->assertCount(1, $users);
+ $this->assertSame('test@getkirby.com', $users[0]['text']);
+ $this->assertSame('/users/test', $users[0]['link']);
+ }
+
+ /**
+ * @covers ::users
+ */
+ public function testUsersWithoutChanges(): void
+ {
+ $dialog = new ChangesDialog();
+ $this->assertSame([], $dialog->users());
+ }
+}
diff --git a/tests/Panel/FileTest.php b/tests/Panel/FileTest.php
index 26ad6f3dfb..91827746e9 100644
--- a/tests/Panel/FileTest.php
+++ b/tests/Panel/FileTest.php
@@ -7,14 +7,18 @@
use Kirby\Cms\Page as ModelPage;
use Kirby\Cms\Site as ModelSite;
use Kirby\Cms\User as ModelUser;
+use Kirby\Content\Lock;
use Kirby\Filesystem\Dir;
use Kirby\TestCase;
-class ModelFileTestForceLocked extends ModelFile
+class FileForceLocked extends ModelFile
{
- public function isLocked(): bool
+ public function lock(): Lock
{
- return true;
+ return new Lock(
+ user: new ModelUser(['email' => 'test@getkirby.com']),
+ modified: time()
+ );
}
}
@@ -550,7 +554,7 @@ public function testOptionsWithLockedFile()
'slug' => 'test'
]);
- $file = new ModelFileTestForceLocked([
+ $file = new FileForceLocked([
'filename' => 'test.jpg',
'parent' => $page
]);
@@ -588,7 +592,6 @@ public function testOptionsWithLockedFile()
'update' => false,
];
- $panel = new File($file);
$this->assertSame($expected, $panel->options(['delete']));
}
diff --git a/tests/Panel/ModelTest.php b/tests/Panel/ModelTest.php
index 41b9973fc7..71c6846aa2 100644
--- a/tests/Panel/ModelTest.php
+++ b/tests/Panel/ModelTest.php
@@ -3,7 +3,6 @@
namespace Kirby\Panel;
use Kirby\Cms\App;
-use Kirby\Cms\ContentLock;
use Kirby\Cms\File as ModelFile;
use Kirby\Cms\Page as ModelPage;
use Kirby\Cms\Site as ModelSite;
@@ -11,61 +10,6 @@
use Kirby\Filesystem\Dir;
use Kirby\TestCase;
-class CustomContentLockIsLocked extends ContentLock
-{
- public function __construct()
- {
- $this->model = new ModelPage(['slug' => 'test']);
- }
-
- public function get(): array
- {
- return ['email' => 'foo@bar.com'];
- }
-
- public function isLocked(): bool
- {
- return true;
- }
-
- public function isUnlocked(): bool
- {
- return false;
- }
-}
-
-class CustomContentLockIsUnlocked extends CustomContentLockIslocked
-{
- public function isUnlocked(): bool
- {
- return true;
- }
-}
-
-class ModelSiteNoLocking extends ModelSite
-{
- public function lock(): ContentLock|null
- {
- return null;
- }
-}
-
-class ModelSiteTestForceLocked extends ModelSite
-{
- public function lock(): ContentLock|null
- {
- return new CustomContentLockIsLocked();
- }
-}
-
-class ModelSiteTestForceUnlocked extends ModelSite
-{
- public function lock(): ContentLock|null
- {
- return new CustomContentLockIsUnlocked();
- }
-}
-
class CustomPanelModel extends Model
{
public function buttons(): array
@@ -137,9 +81,35 @@ public function testContent()
'foo' => 'bar'
]
]);
+
$this->assertSame($content, $panel->content());
}
+ /**
+ * @covers ::__construct
+ * @covers ::content
+ */
+ public function testContentWithChanges()
+ {
+ $panel = new CustomPanelModel(
+ new ModelPage(['slug' => 'test'])
+ );
+
+ $panel->model()->version('latest')->save([
+ 'foo' => 'foo',
+ 'uuid' => 'test'
+ ]);
+
+ $panel->model()->version('changes')->save([
+ 'foo' => 'foobar'
+ ]);
+
+ $this->assertSame([
+ 'foo' => 'foobar',
+ 'uuid' => 'test'
+ ], $panel->content());
+ }
+
/**
* @covers ::dragTextFromCallback
*/
@@ -440,34 +410,6 @@ public function testImagePlaceholder()
$this->assertStringStartsWith('data:image/gif;base64,', Model::imagePlaceholder());
}
- /**
- * @covers ::lock
- */
- public function testLock()
- {
- // content locking not supported
- $site = new ModelSiteNoLocking();
- $this->assertFalse($site->panel()->lock());
-
- Dir::make(static::TMP . '/content');
- $app = $this->app->clone();
- $app->impersonate('kirby');
-
- // no lock or unlock
- $site = new ModelSite();
- $this->assertSame(['state' => null, 'data' => false], $site->panel()->lock());
-
- // lock
- $site = new ModelSiteTestForceLocked();
- $lock = $site->panel()->lock();
- $this->assertSame('lock', $lock['state']);
- $this->assertSame('foo@bar.com', $lock['data']['email']);
-
- // unlock
- $site = new ModelSiteTestForceUnlocked();
- $this->assertSame('unlock', $site->panel()->lock()['state']);
- }
-
/**
* @covers ::model
*/
diff --git a/tests/Panel/PageTest.php b/tests/Panel/PageTest.php
index f01f5cfd8b..8c1b3291ae 100644
--- a/tests/Panel/PageTest.php
+++ b/tests/Panel/PageTest.php
@@ -5,15 +5,20 @@
use Kirby\Cms\App;
use Kirby\Cms\Page as ModelPage;
use Kirby\Cms\Site as ModelSite;
+use Kirby\Cms\User as ModelUser;
+use Kirby\Content\Lock;
use Kirby\Filesystem\Dir;
use Kirby\TestCase;
use Kirby\Toolkit\Str;
-class ModelPageTestForceLocked extends ModelPage
+class PageForceLocked extends ModelPage
{
- public function isLocked(): bool
+ public function lock(): Lock
{
- return true;
+ return new Lock(
+ user: new ModelUser(['email' => 'test@getkirby.com']),
+ modified: time()
+ );
}
}
@@ -401,7 +406,7 @@ public function testOptions()
*/
public function testOptionsWithLockedPage()
{
- $page = new ModelPageTestForceLocked([
+ $page = new PageForceLocked([
'slug' => 'test',
]);
@@ -446,7 +451,6 @@ public function testOptionsWithLockedPage()
'update' => false,
];
- $panel = new Page($page);
$this->assertSame($expected, $panel->options(['preview']));
}
diff --git a/tests/Panel/Ui/Buttons/LanguagesDropdownTest.php b/tests/Panel/Ui/Buttons/LanguagesDropdownTest.php
index 4e69d22740..5ebcc2a3fe 100644
--- a/tests/Panel/Ui/Buttons/LanguagesDropdownTest.php
+++ b/tests/Panel/Ui/Buttons/LanguagesDropdownTest.php
@@ -12,6 +12,31 @@
*/
class LanguagesDropdownTest extends AreaTestCase
{
+ /**
+ * @covers ::hasChanges
+ */
+ public function testHasChanges()
+ {
+ $this->install();
+ $this->installLanguages();
+
+ $page = new Page(['slug' => 'test']);
+ $button = new LanguagesDropdown($page);
+
+ // no changes
+ $this->assertFalse($button->hasChanges());
+
+ // changes in current translation (not considered)
+ $page->version('latest')->save([], 'en');
+ $page->version('changes')->save([], 'en');
+ $this->assertFalse($button->hasChanges());
+
+ // changes in other translations
+ $page->version('latest')->save([], 'de');
+ $page->version('changes')->save([], 'de');
+ $this->assertTrue($button->hasChanges());
+ }
+
/**
* @covers ::option
*/
@@ -23,8 +48,11 @@ public function testOption()
$this->assertSame([
'text' => 'Deutsch',
'code' => 'de',
+ 'link' => '/pages/test?language=de',
'current' => false,
- 'link' => '/pages/test?language=de'
+ 'default' => false,
+ 'changes' => false,
+ 'lock' => false
], $button->option($language));
}
@@ -53,19 +81,36 @@ public function testOptionsMultiLang()
[
'text' => 'English',
'code' => 'en',
+ 'link' => '/pages/test?language=en',
'current' => true,
- 'link' => '/pages/test?language=en'
+ 'default' => true,
+ 'changes' => false,
+ 'lock' => false
],
'-',
[
'text' => 'Deutsch',
'code' => 'de',
+ 'link' => '/pages/test?language=de',
'current' => false,
- 'link' => '/pages/test?language=de'
+ 'default' => false,
+ 'changes' => false,
+ 'lock' => false
]
], $button->options());
}
+ /**
+ * @covers ::props
+ */
+ public function testProps()
+ {
+ $page = new Page(['slug' => 'test']);
+ $button = new LanguagesDropdown($page);
+ $props = $button->props();
+ $this->assertFalse($props['hasChanges']);
+ }
+
/**
* @covers ::render
*/
diff --git a/tests/Panel/Ui/Buttons/PageStatusButtonTest.php b/tests/Panel/Ui/Buttons/PageStatusButtonTest.php
index 235e5a5f7d..69d8fd46ec 100644
--- a/tests/Panel/Ui/Buttons/PageStatusButtonTest.php
+++ b/tests/Panel/Ui/Buttons/PageStatusButtonTest.php
@@ -20,7 +20,7 @@ public function testButtonDraftDisabled()
$page = new Page(['slug' => 'test', 'isDraft' => true]);
$button = new PageStatusButton($page);
- $this->assertSame('k-view-button', $button->component);
+ $this->assertSame('k-status-view-button', $button->component);
$this->assertSame('k-status-view-button k-page-status-button', $button->class);
$this->assertSame('/pages/test/changeStatus', $button->dialog);
$this->assertTrue($button->disabled);
diff --git a/tests/Panel/UserTest.php b/tests/Panel/UserTest.php
index 09d30125c3..1aebd8eb5d 100644
--- a/tests/Panel/UserTest.php
+++ b/tests/Panel/UserTest.php
@@ -5,15 +5,19 @@
use Kirby\Cms\App;
use Kirby\Cms\Blueprint;
use Kirby\Cms\User as ModelUser;
+use Kirby\Content\Lock;
use Kirby\Filesystem\Dir;
use Kirby\TestCase;
use Kirby\Toolkit\Str;
-class ModelUserTestForceLocked extends ModelUser
+class UserForceLocked extends ModelUser
{
- public function isLocked(): bool
+ public function lock(): Lock
{
- return true;
+ return new Lock(
+ user: new ModelUser(['email' => 'test@getkirby.com']),
+ modified: time()
+ );
}
}
@@ -305,7 +309,7 @@ public function testOptions()
*/
public function testOptionsWithLockedUser()
{
- $user = new ModelUserTestForceLocked([
+ $user = new UserForceLocked([
'email' => 'test@getkirby.com',
]);
diff --git a/tests/Plugin/PluginTest.php b/tests/Plugin/PluginTest.php
index 6dadca8fb1..c96df32f15 100644
--- a/tests/Plugin/PluginTest.php
+++ b/tests/Plugin/PluginTest.php
@@ -253,7 +253,14 @@ public function testInfoWhenEmpty()
public function testLicense(): void
{
$plugin = new Plugin(name: 'getkirby/test-plugin');
+ $this->assertInstanceOf(License::class, $license = $plugin->license());
+ }
+
+ public function testLicenseFromString(): void
+ {
+ $plugin = new Plugin(name: 'getkirby/test-plugin', license: 'mit');
$this->assertInstanceOf(License::class, $plugin->license());
+ $this->assertSame('mit', $plugin->license()->name());
}
/**
diff --git a/tests/Toolkit/ComponentTest.php b/tests/Toolkit/ComponentTest.php
index a4e78017d7..c131ff2fdd 100644
--- a/tests/Toolkit/ComponentTest.php
+++ b/tests/Toolkit/ComponentTest.php
@@ -21,6 +21,7 @@ public function tearDown(): void
/**
* @covers ::__construct
* @covers ::__call
+ * @covers ::applyProp
* @covers ::applyProps
*/
public function testProp()
@@ -40,6 +41,7 @@ public function testProp()
}
/**
+ * @covers ::applyProp
* @covers ::applyProps
*/
public function testPropWithDefaultValue()
@@ -59,6 +61,7 @@ public function testPropWithDefaultValue()
}
/**
+ * @covers ::applyProp
* @covers ::applyProps
*/
public function testPropWithFixedValue()
@@ -78,6 +81,7 @@ public function testPropWithFixedValue()
}
/**
+ * @covers ::applyProp
* @covers ::applyProps
*/
public function testPropWithInvalidValue()
@@ -97,6 +101,7 @@ public function testPropWithInvalidValue()
}
/**
+ * @covers ::applyProp
* @covers ::applyProps
*/
public function testPropWithMissingValue()
diff --git a/vendor/bin/yaml-lint.bat b/vendor/bin/yaml-lint.bat
new file mode 100644
index 0000000000..fbce06e043
--- /dev/null
+++ b/vendor/bin/yaml-lint.bat
@@ -0,0 +1,5 @@
+@ECHO OFF
+setlocal DISABLEDELAYEDEXPANSION
+SET BIN_TARGET=%~dp0/yaml-lint
+SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
+php "%BIN_TARGET%" %*
diff --git a/vendor/claviska/simpleimage/src/claviska/SimpleImage.php b/vendor/claviska/simpleimage/src/claviska/SimpleImage.php
index c9c0c7b0bf..5c08c584c3 100644
--- a/vendor/claviska/simpleimage/src/claviska/SimpleImage.php
+++ b/vendor/claviska/simpleimage/src/claviska/SimpleImage.php
@@ -331,7 +331,7 @@ public function fromString(string $string): SimpleImage|static
*
* @throws Exception Thrown when WEBP support is not enabled or unsupported format.
*/
- public function generate(string $mimeType = null, array|int $options = 100): array
+ public function generate(string|null $mimeType = null, array|int $options = 100): array
{
// Format defaults to the original mime type
$mimeType = $mimeType ?: $this->mimeType;
@@ -473,7 +473,7 @@ public function generate(string $mimeType = null, array|int $options = 100): arr
*
* @throws Exception
*/
- public function toDataUri(string $mimeType = null, array|int $options = 100): string
+ public function toDataUri(string|null $mimeType = null, array|int $options = 100): string
{
$image = $this->generate($mimeType, $options);
@@ -490,7 +490,7 @@ public function toDataUri(string $mimeType = null, array|int $options = 100): st
*
* @throws Exception
*/
- public function toDownload(string $filename, string $mimeType = null, array|int $options = 100): static
+ public function toDownload(string $filename, string|null $mimeType = null, array|int $options = 100): static
{
$image = $this->generate($mimeType, $options);
@@ -517,7 +517,7 @@ public function toDownload(string $filename, string $mimeType = null, array|int
*
* @throws Exception Thrown if failed write to file.
*/
- public function toFile(string $file, string $mimeType = null, array|int $options = 100): static
+ public function toFile(string $file, string|null $mimeType = null, array|int $options = 100): static
{
$image = $this->generate($mimeType, $options);
@@ -538,7 +538,7 @@ public function toFile(string $file, string $mimeType = null, array|int $options
*
* @throws Exception
*/
- public function toScreen(string $mimeType = null, array|int $options = 100): static
+ public function toScreen(string|null $mimeType = null, array|int $options = 100): static
{
$image = $this->generate($mimeType, $options);
@@ -557,7 +557,7 @@ public function toScreen(string $mimeType = null, array|int $options = 100): sta
*
* @throws Exception
*/
- public function toString(string $mimeType = null, array|int $options = 100): string
+ public function toString(string|null $mimeType = null, array|int $options = 100): string
{
return $this->generate($mimeType, $options)['data'];
}
@@ -975,7 +975,7 @@ public function overlay(string|SimpleImage $overlay, string $anchor = 'center',
* @param int|null $height The new image height.
* @return SimpleImage
*/
- public function resize(int $width = null, int $height = null): static
+ public function resize(int|null $width = null, int|null $height = null): static
{
// No dimensions specified
if (! $width && ! $height) {
@@ -1027,7 +1027,7 @@ public function resize(int $width = null, int $height = null): static
* @param int|null $res_y The vertical resolution in DPI
* @return SimpleImage
*/
- public function resolution(int $res_x, int $res_y = null): static
+ public function resolution(int $res_x, int|null $res_y = null): static
{
if (is_null($res_y)) {
imageresolution($this->image, $res_x);
@@ -1087,7 +1087,7 @@ public function rotate(int $angle, string|array $backgroundColor = 'transparent'
*
* @throws Exception
*/
- public function text(string $text, array $options, array &$boundary = null): static
+ public function text(string $text, array $options, array|null &$boundary = null): static
{
// Check for freetype support
if (! function_exists('imagettftext')) {
@@ -2196,7 +2196,7 @@ public static function darkenColor(string|array $color, int $amount): array
*
* @throws Exception Thrown if library \League\ColorExtractor is missing.
*/
- public function extractColors(int $count = 5, string|array $backgroundColor = null): array
+ public function extractColors(int $count = 5, string|array|null $backgroundColor = null): array
{
// Check for required library
if (! class_exists('\\'.ColorExtractor::class)) {
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
index 1de5df87ef..765ee12590 100644
--- a/vendor/composer/autoload_classmap.php
+++ b/vendor/composer/autoload_classmap.php
@@ -23,6 +23,7 @@
'Composer\\Semver\\VersionParser' => $vendorDir . '/composer/semver/src/VersionParser.php',
'Kirby\\Api\\Api' => $baseDir . '/src/Api/Api.php',
'Kirby\\Api\\Collection' => $baseDir . '/src/Api/Collection.php',
+ 'Kirby\\Api\\Controller\\Changes' => $baseDir . '/src/Api/Controller/Changes.php',
'Kirby\\Api\\Model' => $baseDir . '/src/Api/Model.php',
'Kirby\\Api\\Upload' => $baseDir . '/src/Api/Upload.php',
'Kirby\\Blueprint\\Collection' => $baseDir . '/src/Blueprint/Collection.php',
@@ -61,8 +62,6 @@
'Kirby\\Cms\\Blueprint' => $baseDir . '/src/Cms/Blueprint.php',
'Kirby\\Cms\\Collection' => $baseDir . '/src/Cms/Collection.php',
'Kirby\\Cms\\Collections' => $baseDir . '/src/Cms/Collections.php',
- 'Kirby\\Cms\\ContentLock' => $baseDir . '/src/Cms/ContentLock.php',
- 'Kirby\\Cms\\ContentLocks' => $baseDir . '/src/Cms/ContentLocks.php',
'Kirby\\Cms\\Core' => $baseDir . '/src/Cms/Core.php',
'Kirby\\Cms\\Email' => $baseDir . '/src/Cms/Email.php',
'Kirby\\Cms\\Event' => $baseDir . '/src/Cms/Event.php',
@@ -153,16 +152,18 @@
'Kirby\\ComposerInstaller\\PluginInstaller' => $vendorDir . '/getkirby/composer-installer/src/ComposerInstaller/PluginInstaller.php',
'Kirby\\Content\\Changes' => $baseDir . '/src/Content/Changes.php',
'Kirby\\Content\\Content' => $baseDir . '/src/Content/Content.php',
- 'Kirby\\Content\\ContentStorageHandler' => $baseDir . '/src/Content/ContentStorageHandler.php',
'Kirby\\Content\\ContentTranslation' => $baseDir . '/src/Content/ContentTranslation.php',
'Kirby\\Content\\Field' => $baseDir . '/src/Content/Field.php',
- 'Kirby\\Content\\ImmutableMemoryContentStorageHandler' => $baseDir . '/src/Content/ImmutableMemoryContentStorageHandler.php',
- 'Kirby\\Content\\MemoryContentStorageHandler' => $baseDir . '/src/Content/MemoryContentStorageHandler.php',
- 'Kirby\\Content\\PlainTextContentStorageHandler' => $baseDir . '/src/Content/PlainTextContentStorageHandler.php',
+ 'Kirby\\Content\\ImmutableMemoryStorage' => $baseDir . '/src/Content/ImmutableMemoryStorage.php',
+ 'Kirby\\Content\\Lock' => $baseDir . '/src/Content/Lock.php',
+ 'Kirby\\Content\\MemoryStorage' => $baseDir . '/src/Content/MemoryStorage.php',
+ 'Kirby\\Content\\PlainTextStorage' => $baseDir . '/src/Content/PlainTextStorage.php',
+ 'Kirby\\Content\\Storage' => $baseDir . '/src/Content/Storage.php',
'Kirby\\Content\\Translation' => $baseDir . '/src/Content/Translation.php',
'Kirby\\Content\\Translations' => $baseDir . '/src/Content/Translations.php',
'Kirby\\Content\\Version' => $baseDir . '/src/Content/Version.php',
'Kirby\\Content\\VersionId' => $baseDir . '/src/Content/VersionId.php',
+ 'Kirby\\Content\\VersionRules' => $baseDir . '/src/Content/VersionRules.php',
'Kirby\\Data\\Data' => $baseDir . '/src/Data/Data.php',
'Kirby\\Data\\Handler' => $baseDir . '/src/Data/Handler.php',
'Kirby\\Data\\Json' => $baseDir . '/src/Data/Json.php',
@@ -204,9 +205,15 @@
'Kirby\\Form\\Field\\LayoutField' => $baseDir . '/src/Form/Field/LayoutField.php',
'Kirby\\Form\\Fields' => $baseDir . '/src/Form/Fields.php',
'Kirby\\Form\\Form' => $baseDir . '/src/Form/Form.php',
+ 'Kirby\\Form\\Mixin\\Api' => $baseDir . '/src/Form/Mixin/Api.php',
'Kirby\\Form\\Mixin\\EmptyState' => $baseDir . '/src/Form/Mixin/EmptyState.php',
'Kirby\\Form\\Mixin\\Max' => $baseDir . '/src/Form/Mixin/Max.php',
'Kirby\\Form\\Mixin\\Min' => $baseDir . '/src/Form/Mixin/Min.php',
+ 'Kirby\\Form\\Mixin\\Model' => $baseDir . '/src/Form/Mixin/Model.php',
+ 'Kirby\\Form\\Mixin\\Translatable' => $baseDir . '/src/Form/Mixin/Translatable.php',
+ 'Kirby\\Form\\Mixin\\Validation' => $baseDir . '/src/Form/Mixin/Validation.php',
+ 'Kirby\\Form\\Mixin\\Value' => $baseDir . '/src/Form/Mixin/Value.php',
+ 'Kirby\\Form\\Mixin\\When' => $baseDir . '/src/Form/Mixin/When.php',
'Kirby\\Form\\Validations' => $baseDir . '/src/Form/Validations.php',
'Kirby\\Http\\Cookie' => $baseDir . '/src/Http/Cookie.php',
'Kirby\\Http\\Environment' => $baseDir . '/src/Http/Environment.php',
@@ -249,7 +256,6 @@
'Kirby\\Option\\OptionsQuery' => $baseDir . '/src/Option/OptionsQuery.php',
'Kirby\\Panel\\Assets' => $baseDir . '/src/Panel/Assets.php',
'Kirby\\Panel\\ChangesDialog' => $baseDir . '/src/Panel/ChangesDialog.php',
- 'Kirby\\Panel\\Controller\\Changes' => $baseDir . '/src/Panel/Controller/Changes.php',
'Kirby\\Panel\\Controller\\PageTree' => $baseDir . '/src/Panel/Controller/PageTree.php',
'Kirby\\Panel\\Controller\\Search' => $baseDir . '/src/Panel/Controller/Search.php',
'Kirby\\Panel\\Dialog' => $baseDir . '/src/Panel/Dialog.php',
@@ -282,6 +288,7 @@
'Kirby\\Panel\\Ui\\Buttons\\LanguagesDropdown' => $baseDir . '/src/Panel/Ui/Buttons/LanguagesDropdown.php',
'Kirby\\Panel\\Ui\\Buttons\\PageStatusButton' => $baseDir . '/src/Panel/Ui/Buttons/PageStatusButton.php',
'Kirby\\Panel\\Ui\\Buttons\\PreviewButton' => $baseDir . '/src/Panel/Ui/Buttons/PreviewButton.php',
+ 'Kirby\\Panel\\Ui\\Buttons\\PreviewDropdownButton' => $baseDir . '/src/Panel/Ui/Buttons/PreviewDropdownButton.php',
'Kirby\\Panel\\Ui\\Buttons\\SettingsButton' => $baseDir . '/src/Panel/Ui/Buttons/SettingsButton.php',
'Kirby\\Panel\\Ui\\Buttons\\ViewButton' => $baseDir . '/src/Panel/Ui/Buttons/ViewButton.php',
'Kirby\\Panel\\Ui\\Buttons\\ViewButtons' => $baseDir . '/src/Panel/Ui/Buttons/ViewButtons.php',
@@ -303,6 +310,8 @@
'Kirby\\Parsley\\Schema\\Plain' => $baseDir . '/src/Parsley/Schema/Plain.php',
'Kirby\\Plugin\\Asset' => $baseDir . '/src/Plugin/Asset.php',
'Kirby\\Plugin\\Assets' => $baseDir . '/src/Plugin/Assets.php',
+ 'Kirby\\Plugin\\License' => $baseDir . '/src/Plugin/License.php',
+ 'Kirby\\Plugin\\LicenseStatus' => $baseDir . '/src/Plugin/LicenseStatus.php',
'Kirby\\Plugin\\Plugin' => $baseDir . '/src/Plugin/Plugin.php',
'Kirby\\Query\\Argument' => $baseDir . '/src/Query/Argument.php',
'Kirby\\Query\\Arguments' => $baseDir . '/src/Query/Arguments.php',
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
index 2fe4c1baaf..1ef5089861 100644
--- a/vendor/composer/autoload_static.php
+++ b/vendor/composer/autoload_static.php
@@ -143,6 +143,7 @@ class ComposerStaticInit0bf5c8a9cfa251a218fc581ac888fe35
'Composer\\Semver\\VersionParser' => __DIR__ . '/..' . '/composer/semver/src/VersionParser.php',
'Kirby\\Api\\Api' => __DIR__ . '/../..' . '/src/Api/Api.php',
'Kirby\\Api\\Collection' => __DIR__ . '/../..' . '/src/Api/Collection.php',
+ 'Kirby\\Api\\Controller\\Changes' => __DIR__ . '/../..' . '/src/Api/Controller/Changes.php',
'Kirby\\Api\\Model' => __DIR__ . '/../..' . '/src/Api/Model.php',
'Kirby\\Api\\Upload' => __DIR__ . '/../..' . '/src/Api/Upload.php',
'Kirby\\Blueprint\\Collection' => __DIR__ . '/../..' . '/src/Blueprint/Collection.php',
@@ -181,8 +182,6 @@ class ComposerStaticInit0bf5c8a9cfa251a218fc581ac888fe35
'Kirby\\Cms\\Blueprint' => __DIR__ . '/../..' . '/src/Cms/Blueprint.php',
'Kirby\\Cms\\Collection' => __DIR__ . '/../..' . '/src/Cms/Collection.php',
'Kirby\\Cms\\Collections' => __DIR__ . '/../..' . '/src/Cms/Collections.php',
- 'Kirby\\Cms\\ContentLock' => __DIR__ . '/../..' . '/src/Cms/ContentLock.php',
- 'Kirby\\Cms\\ContentLocks' => __DIR__ . '/../..' . '/src/Cms/ContentLocks.php',
'Kirby\\Cms\\Core' => __DIR__ . '/../..' . '/src/Cms/Core.php',
'Kirby\\Cms\\Email' => __DIR__ . '/../..' . '/src/Cms/Email.php',
'Kirby\\Cms\\Event' => __DIR__ . '/../..' . '/src/Cms/Event.php',
@@ -273,16 +272,18 @@ class ComposerStaticInit0bf5c8a9cfa251a218fc581ac888fe35
'Kirby\\ComposerInstaller\\PluginInstaller' => __DIR__ . '/..' . '/getkirby/composer-installer/src/ComposerInstaller/PluginInstaller.php',
'Kirby\\Content\\Changes' => __DIR__ . '/../..' . '/src/Content/Changes.php',
'Kirby\\Content\\Content' => __DIR__ . '/../..' . '/src/Content/Content.php',
- 'Kirby\\Content\\ContentStorageHandler' => __DIR__ . '/../..' . '/src/Content/ContentStorageHandler.php',
'Kirby\\Content\\ContentTranslation' => __DIR__ . '/../..' . '/src/Content/ContentTranslation.php',
'Kirby\\Content\\Field' => __DIR__ . '/../..' . '/src/Content/Field.php',
- 'Kirby\\Content\\ImmutableMemoryContentStorageHandler' => __DIR__ . '/../..' . '/src/Content/ImmutableMemoryContentStorageHandler.php',
- 'Kirby\\Content\\MemoryContentStorageHandler' => __DIR__ . '/../..' . '/src/Content/MemoryContentStorageHandler.php',
- 'Kirby\\Content\\PlainTextContentStorageHandler' => __DIR__ . '/../..' . '/src/Content/PlainTextContentStorageHandler.php',
+ 'Kirby\\Content\\ImmutableMemoryStorage' => __DIR__ . '/../..' . '/src/Content/ImmutableMemoryStorage.php',
+ 'Kirby\\Content\\Lock' => __DIR__ . '/../..' . '/src/Content/Lock.php',
+ 'Kirby\\Content\\MemoryStorage' => __DIR__ . '/../..' . '/src/Content/MemoryStorage.php',
+ 'Kirby\\Content\\PlainTextStorage' => __DIR__ . '/../..' . '/src/Content/PlainTextStorage.php',
+ 'Kirby\\Content\\Storage' => __DIR__ . '/../..' . '/src/Content/Storage.php',
'Kirby\\Content\\Translation' => __DIR__ . '/../..' . '/src/Content/Translation.php',
'Kirby\\Content\\Translations' => __DIR__ . '/../..' . '/src/Content/Translations.php',
'Kirby\\Content\\Version' => __DIR__ . '/../..' . '/src/Content/Version.php',
'Kirby\\Content\\VersionId' => __DIR__ . '/../..' . '/src/Content/VersionId.php',
+ 'Kirby\\Content\\VersionRules' => __DIR__ . '/../..' . '/src/Content/VersionRules.php',
'Kirby\\Data\\Data' => __DIR__ . '/../..' . '/src/Data/Data.php',
'Kirby\\Data\\Handler' => __DIR__ . '/../..' . '/src/Data/Handler.php',
'Kirby\\Data\\Json' => __DIR__ . '/../..' . '/src/Data/Json.php',
@@ -324,9 +325,15 @@ class ComposerStaticInit0bf5c8a9cfa251a218fc581ac888fe35
'Kirby\\Form\\Field\\LayoutField' => __DIR__ . '/../..' . '/src/Form/Field/LayoutField.php',
'Kirby\\Form\\Fields' => __DIR__ . '/../..' . '/src/Form/Fields.php',
'Kirby\\Form\\Form' => __DIR__ . '/../..' . '/src/Form/Form.php',
+ 'Kirby\\Form\\Mixin\\Api' => __DIR__ . '/../..' . '/src/Form/Mixin/Api.php',
'Kirby\\Form\\Mixin\\EmptyState' => __DIR__ . '/../..' . '/src/Form/Mixin/EmptyState.php',
'Kirby\\Form\\Mixin\\Max' => __DIR__ . '/../..' . '/src/Form/Mixin/Max.php',
'Kirby\\Form\\Mixin\\Min' => __DIR__ . '/../..' . '/src/Form/Mixin/Min.php',
+ 'Kirby\\Form\\Mixin\\Model' => __DIR__ . '/../..' . '/src/Form/Mixin/Model.php',
+ 'Kirby\\Form\\Mixin\\Translatable' => __DIR__ . '/../..' . '/src/Form/Mixin/Translatable.php',
+ 'Kirby\\Form\\Mixin\\Validation' => __DIR__ . '/../..' . '/src/Form/Mixin/Validation.php',
+ 'Kirby\\Form\\Mixin\\Value' => __DIR__ . '/../..' . '/src/Form/Mixin/Value.php',
+ 'Kirby\\Form\\Mixin\\When' => __DIR__ . '/../..' . '/src/Form/Mixin/When.php',
'Kirby\\Form\\Validations' => __DIR__ . '/../..' . '/src/Form/Validations.php',
'Kirby\\Http\\Cookie' => __DIR__ . '/../..' . '/src/Http/Cookie.php',
'Kirby\\Http\\Environment' => __DIR__ . '/../..' . '/src/Http/Environment.php',
@@ -369,7 +376,6 @@ class ComposerStaticInit0bf5c8a9cfa251a218fc581ac888fe35
'Kirby\\Option\\OptionsQuery' => __DIR__ . '/../..' . '/src/Option/OptionsQuery.php',
'Kirby\\Panel\\Assets' => __DIR__ . '/../..' . '/src/Panel/Assets.php',
'Kirby\\Panel\\ChangesDialog' => __DIR__ . '/../..' . '/src/Panel/ChangesDialog.php',
- 'Kirby\\Panel\\Controller\\Changes' => __DIR__ . '/../..' . '/src/Panel/Controller/Changes.php',
'Kirby\\Panel\\Controller\\PageTree' => __DIR__ . '/../..' . '/src/Panel/Controller/PageTree.php',
'Kirby\\Panel\\Controller\\Search' => __DIR__ . '/../..' . '/src/Panel/Controller/Search.php',
'Kirby\\Panel\\Dialog' => __DIR__ . '/../..' . '/src/Panel/Dialog.php',
@@ -402,6 +408,7 @@ class ComposerStaticInit0bf5c8a9cfa251a218fc581ac888fe35
'Kirby\\Panel\\Ui\\Buttons\\LanguagesDropdown' => __DIR__ . '/../..' . '/src/Panel/Ui/Buttons/LanguagesDropdown.php',
'Kirby\\Panel\\Ui\\Buttons\\PageStatusButton' => __DIR__ . '/../..' . '/src/Panel/Ui/Buttons/PageStatusButton.php',
'Kirby\\Panel\\Ui\\Buttons\\PreviewButton' => __DIR__ . '/../..' . '/src/Panel/Ui/Buttons/PreviewButton.php',
+ 'Kirby\\Panel\\Ui\\Buttons\\PreviewDropdownButton' => __DIR__ . '/../..' . '/src/Panel/Ui/Buttons/PreviewDropdownButton.php',
'Kirby\\Panel\\Ui\\Buttons\\SettingsButton' => __DIR__ . '/../..' . '/src/Panel/Ui/Buttons/SettingsButton.php',
'Kirby\\Panel\\Ui\\Buttons\\ViewButton' => __DIR__ . '/../..' . '/src/Panel/Ui/Buttons/ViewButton.php',
'Kirby\\Panel\\Ui\\Buttons\\ViewButtons' => __DIR__ . '/../..' . '/src/Panel/Ui/Buttons/ViewButtons.php',
@@ -423,6 +430,8 @@ class ComposerStaticInit0bf5c8a9cfa251a218fc581ac888fe35
'Kirby\\Parsley\\Schema\\Plain' => __DIR__ . '/../..' . '/src/Parsley/Schema/Plain.php',
'Kirby\\Plugin\\Asset' => __DIR__ . '/../..' . '/src/Plugin/Asset.php',
'Kirby\\Plugin\\Assets' => __DIR__ . '/../..' . '/src/Plugin/Assets.php',
+ 'Kirby\\Plugin\\License' => __DIR__ . '/../..' . '/src/Plugin/License.php',
+ 'Kirby\\Plugin\\LicenseStatus' => __DIR__ . '/../..' . '/src/Plugin/LicenseStatus.php',
'Kirby\\Plugin\\Plugin' => __DIR__ . '/../..' . '/src/Plugin/Plugin.php',
'Kirby\\Query\\Argument' => __DIR__ . '/../..' . '/src/Query/Argument.php',
'Kirby\\Query\\Arguments' => __DIR__ . '/../..' . '/src/Query/Arguments.php',
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index af894f9d6e..42decfba67 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -64,17 +64,17 @@
},
{
"name": "claviska/simpleimage",
- "version": "4.2.0",
- "version_normalized": "4.2.0.0",
+ "version": "4.2.1",
+ "version_normalized": "4.2.1.0",
"source": {
"type": "git",
"url": "https://github.com/claviska/SimpleImage.git",
- "reference": "dfbe53c01dae8467468ef2b817c09b786a7839d2"
+ "reference": "ec6d5021e5a7153a2520d64c59b86b6f3c4157c5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/claviska/SimpleImage/zipball/dfbe53c01dae8467468ef2b817c09b786a7839d2",
- "reference": "dfbe53c01dae8467468ef2b817c09b786a7839d2",
+ "url": "https://api.github.com/repos/claviska/SimpleImage/zipball/ec6d5021e5a7153a2520d64c59b86b6f3c4157c5",
+ "reference": "ec6d5021e5a7153a2520d64c59b86b6f3c4157c5",
"shasum": ""
},
"require": {
@@ -86,7 +86,7 @@
"laravel/pint": "^1.5",
"phpstan/phpstan": "^1.10"
},
- "time": "2024-04-15T16:07:16+00:00",
+ "time": "2024-11-22T13:25:03+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -108,7 +108,7 @@
"description": "A PHP class that makes working with images as simple as possible.",
"support": {
"issues": "https://github.com/claviska/SimpleImage/issues",
- "source": "https://github.com/claviska/SimpleImage/tree/4.2.0"
+ "source": "https://github.com/claviska/SimpleImage/tree/4.2.1"
},
"funding": [
{
@@ -204,33 +204,33 @@
},
{
"name": "filp/whoops",
- "version": "2.15.4",
- "version_normalized": "2.15.4.0",
+ "version": "2.16.0",
+ "version_normalized": "2.16.0.0",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
- "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546"
+ "reference": "befcdc0e5dce67252aa6322d82424be928214fa2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546",
- "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546",
+ "url": "https://api.github.com/repos/filp/whoops/zipball/befcdc0e5dce67252aa6322d82424be928214fa2",
+ "reference": "befcdc0e5dce67252aa6322d82424be928214fa2",
"shasum": ""
},
"require": {
- "php": "^5.5.9 || ^7.0 || ^8.0",
+ "php": "^7.1 || ^8.0",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
- "mockery/mockery": "^0.9 || ^1.0",
- "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
- "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
+ "mockery/mockery": "^1.0",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
+ "symfony/var-dumper": "^4.0 || ^5.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
"whoops/soap": "Formats errors as SOAP responses"
},
- "time": "2023-11-03T12:00:00+00:00",
+ "time": "2024-09-25T12:00:00+00:00",
"type": "library",
"extra": {
"branch-alias": {
@@ -266,7 +266,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
- "source": "https://github.com/filp/whoops/tree/2.15.4"
+ "source": "https://github.com/filp/whoops/tree/2.16.0"
},
"funding": [
{
@@ -328,36 +328,36 @@
},
{
"name": "laminas/laminas-escaper",
- "version": "2.13.0",
- "version_normalized": "2.13.0.0",
+ "version": "2.14.0",
+ "version_normalized": "2.14.0.0",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-escaper.git",
- "reference": "af459883f4018d0f8a0c69c7a209daef3bf973ba"
+ "reference": "0f7cb975f4443cf22f33408925c231225cfba8cb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/af459883f4018d0f8a0c69c7a209daef3bf973ba",
- "reference": "af459883f4018d0f8a0c69c7a209daef3bf973ba",
+ "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/0f7cb975f4443cf22f33408925c231225cfba8cb",
+ "reference": "0f7cb975f4443cf22f33408925c231225cfba8cb",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-mbstring": "*",
- "php": "~8.1.0 || ~8.2.0 || ~8.3.0"
+ "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
},
"conflict": {
"zendframework/zend-escaper": "*"
},
"require-dev": {
- "infection/infection": "^0.27.0",
- "laminas/laminas-coding-standard": "~2.5.0",
+ "infection/infection": "^0.27.9",
+ "laminas/laminas-coding-standard": "~3.0.0",
"maglnet/composer-require-checker": "^3.8.0",
- "phpunit/phpunit": "^9.6.7",
- "psalm/plugin-phpunit": "^0.18.4",
- "vimeo/psalm": "^5.9"
+ "phpunit/phpunit": "^9.6.16",
+ "psalm/plugin-phpunit": "^0.19.0",
+ "vimeo/psalm": "^5.21.1"
},
- "time": "2023-10-10T08:35:13+00:00",
+ "time": "2024-10-24T10:12:53+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -514,17 +514,17 @@
},
{
"name": "phpmailer/phpmailer",
- "version": "v6.9.1",
- "version_normalized": "6.9.1.0",
+ "version": "v6.9.2",
+ "version_normalized": "6.9.2.0",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
- "reference": "039de174cd9c17a8389754d3b877a2ed22743e18"
+ "reference": "a7b17b42fa4887c92146243f3d2f4ccb962af17c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18",
- "reference": "039de174cd9c17a8389754d3b877a2ed22743e18",
+ "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a7b17b42fa4887c92146243f3d2f4ccb962af17c",
+ "reference": "a7b17b42fa4887c92146243f3d2f4ccb962af17c",
"shasum": ""
},
"require": {
@@ -554,7 +554,7 @@
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
},
- "time": "2023-11-25T22:23:28+00:00",
+ "time": "2024-10-09T10:07:50+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -586,7 +586,7 @@
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"support": {
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
- "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1"
+ "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.2"
},
"funding": [
{
@@ -986,17 +986,17 @@
},
{
"name": "symfony/yaml",
- "version": "v7.1.5",
- "version_normalized": "7.1.5.0",
+ "version": "v7.1.6",
+ "version_normalized": "7.1.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "4e561c316e135e053bd758bf3b3eb291d9919de4"
+ "reference": "3ced3f29e4f0d6bce2170ff26719f1fe9aacc671"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/4e561c316e135e053bd758bf3b3eb291d9919de4",
- "reference": "4e561c316e135e053bd758bf3b3eb291d9919de4",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/3ced3f29e4f0d6bce2170ff26719f1fe9aacc671",
+ "reference": "3ced3f29e4f0d6bce2170ff26719f1fe9aacc671",
"shasum": ""
},
"require": {
@@ -1009,7 +1009,7 @@
"require-dev": {
"symfony/console": "^6.4|^7.0"
},
- "time": "2024-09-17T12:49:58+00:00",
+ "time": "2024-09-25T14:20:29+00:00",
"bin": [
"Resources/bin/yaml-lint"
],
@@ -1040,7 +1040,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v7.1.5"
+ "source": "https://github.com/symfony/yaml/tree/v7.1.6"
},
"funding": [
{
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index 3d1cb149ca..930cf32d7e 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -1,8 +1,8 @@
array(
'name' => 'getkirby/cms',
- 'pretty_version' => '5.0.0-alpha.3',
- 'version' => '5.0.0.0-alpha3',
+ 'pretty_version' => '5.0.0-alpha.4',
+ 'version' => '5.0.0.0-alpha4',
'reference' => NULL,
'type' => 'kirby-cms',
'install_path' => __DIR__ . '/../../',
@@ -20,9 +20,9 @@
'dev_requirement' => false,
),
'claviska/simpleimage' => array(
- 'pretty_version' => '4.2.0',
- 'version' => '4.2.0.0',
- 'reference' => 'dfbe53c01dae8467468ef2b817c09b786a7839d2',
+ 'pretty_version' => '4.2.1',
+ 'version' => '4.2.1.0',
+ 'reference' => 'ec6d5021e5a7153a2520d64c59b86b6f3c4157c5',
'type' => 'library',
'install_path' => __DIR__ . '/../claviska/simpleimage',
'aliases' => array(),
@@ -38,17 +38,17 @@
'dev_requirement' => false,
),
'filp/whoops' => array(
- 'pretty_version' => '2.15.4',
- 'version' => '2.15.4.0',
- 'reference' => 'a139776fa3f5985a50b509f2a02ff0f709d2a546',
+ 'pretty_version' => '2.16.0',
+ 'version' => '2.16.0.0',
+ 'reference' => 'befcdc0e5dce67252aa6322d82424be928214fa2',
'type' => 'library',
'install_path' => __DIR__ . '/../filp/whoops',
'aliases' => array(),
'dev_requirement' => false,
),
'getkirby/cms' => array(
- 'pretty_version' => '5.0.0-alpha.3',
- 'version' => '5.0.0.0-alpha3',
+ 'pretty_version' => '5.0.0-alpha.4',
+ 'version' => '5.0.0.0-alpha4',
'reference' => NULL,
'type' => 'kirby-cms',
'install_path' => __DIR__ . '/../../',
@@ -65,9 +65,9 @@
'dev_requirement' => false,
),
'laminas/laminas-escaper' => array(
- 'pretty_version' => '2.13.0',
- 'version' => '2.13.0.0',
- 'reference' => 'af459883f4018d0f8a0c69c7a209daef3bf973ba',
+ 'pretty_version' => '2.14.0',
+ 'version' => '2.14.0.0',
+ 'reference' => '0f7cb975f4443cf22f33408925c231225cfba8cb',
'type' => 'library',
'install_path' => __DIR__ . '/../laminas/laminas-escaper',
'aliases' => array(),
@@ -98,9 +98,9 @@
'dev_requirement' => false,
),
'phpmailer/phpmailer' => array(
- 'pretty_version' => 'v6.9.1',
- 'version' => '6.9.1.0',
- 'reference' => '039de174cd9c17a8389754d3b877a2ed22743e18',
+ 'pretty_version' => 'v6.9.2',
+ 'version' => '6.9.2.0',
+ 'reference' => 'a7b17b42fa4887c92146243f3d2f4ccb962af17c',
'type' => 'library',
'install_path' => __DIR__ . '/../phpmailer/phpmailer',
'aliases' => array(),
@@ -158,9 +158,9 @@
),
),
'symfony/yaml' => array(
- 'pretty_version' => 'v7.1.5',
- 'version' => '7.1.5.0',
- 'reference' => '4e561c316e135e053bd758bf3b3eb291d9919de4',
+ 'pretty_version' => 'v7.1.6',
+ 'version' => '7.1.6.0',
+ 'reference' => '3ced3f29e4f0d6bce2170ff26719f1fe9aacc671',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/yaml',
'aliases' => array(),
diff --git a/vendor/filp/whoops/composer.json b/vendor/filp/whoops/composer.json
index 06b5c756bf..c72fab0015 100644
--- a/vendor/filp/whoops/composer.json
+++ b/vendor/filp/whoops/composer.json
@@ -15,13 +15,13 @@
"test": "phpunit --testdox tests"
},
"require": {
- "php": "^5.5.9 || ^7.0 || ^8.0",
+ "php": "^7.1 || ^8.0",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
- "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
- "mockery/mockery": "^0.9 || ^1.0",
- "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
+ "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
+ "mockery/mockery": "^1.0",
+ "symfony/var-dumper": "^4.0 || ^5.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
diff --git a/vendor/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php b/vendor/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php
index 167407e81a..b739ac0696 100644
--- a/vendor/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php
+++ b/vendor/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php
@@ -383,7 +383,7 @@ public function addDataTableCallback($label, /* callable */ $callback)
throw new InvalidArgumentException('Expecting callback argument to be callable');
}
- $this->extraTables[$label] = function (\Whoops\Inspector\InspectorInterface $inspector = null) use ($callback) {
+ $this->extraTables[$label] = function (?\Whoops\Inspector\InspectorInterface $inspector = null) use ($callback) {
try {
$result = call_user_func($callback, $inspector);
diff --git a/vendor/filp/whoops/src/Whoops/Run.php b/vendor/filp/whoops/src/Whoops/Run.php
index 08627680fe..7be63affc2 100644
--- a/vendor/filp/whoops/src/Whoops/Run.php
+++ b/vendor/filp/whoops/src/Whoops/Run.php
@@ -81,7 +81,7 @@ final class Run implements RunInterface
*/
private $frameFilters = [];
- public function __construct(SystemFacade $system = null)
+ public function __construct(?SystemFacade $system = null)
{
$this->system = $system ?: new SystemFacade;
$this->inspectorFactory = new InspectorFactory();
diff --git a/vendor/filp/whoops/src/Whoops/Util/TemplateHelper.php b/vendor/filp/whoops/src/Whoops/Util/TemplateHelper.php
index 8e4df32802..5612c0b771 100644
--- a/vendor/filp/whoops/src/Whoops/Util/TemplateHelper.php
+++ b/vendor/filp/whoops/src/Whoops/Util/TemplateHelper.php
@@ -233,7 +233,7 @@ public function slug($original)
*
* @param string $template
*/
- public function render($template, array $additionalVariables = null)
+ public function render($template, ?array $additionalVariables = null)
{
$variables = $this->getVariables();
diff --git a/vendor/laminas/laminas-escaper/composer.json b/vendor/laminas/laminas-escaper/composer.json
index 16cf063377..38f386956a 100644
--- a/vendor/laminas/laminas-escaper/composer.json
+++ b/vendor/laminas/laminas-escaper/composer.json
@@ -29,17 +29,17 @@
"extra": {
},
"require": {
- "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
+ "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
"ext-ctype": "*",
"ext-mbstring": "*"
},
"require-dev": {
- "infection/infection": "^0.27.0",
- "laminas/laminas-coding-standard": "~2.5.0",
+ "infection/infection": "^0.27.9",
+ "laminas/laminas-coding-standard": "~3.0.0",
"maglnet/composer-require-checker": "^3.8.0",
- "phpunit/phpunit": "^9.6.7",
- "psalm/plugin-phpunit": "^0.18.4",
- "vimeo/psalm": "^5.9"
+ "phpunit/phpunit": "^9.6.16",
+ "psalm/plugin-phpunit": "^0.19.0",
+ "vimeo/psalm": "^5.21.1"
},
"autoload": {
"psr-4": {
diff --git a/vendor/phpmailer/phpmailer/composer.json b/vendor/phpmailer/phpmailer/composer.json
index fa170a0bbb..7b008b7c57 100644
--- a/vendor/phpmailer/phpmailer/composer.json
+++ b/vendor/phpmailer/phpmailer/composer.json
@@ -28,7 +28,8 @@
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
- }
+ },
+ "lock": false
},
"require": {
"php": ">=5.5.0",
diff --git a/vendor/phpmailer/phpmailer/get_oauth_token.php b/vendor/phpmailer/phpmailer/get_oauth_token.php
index cda0445c6b..0e54a00b63 100644
--- a/vendor/phpmailer/phpmailer/get_oauth_token.php
+++ b/vendor/phpmailer/phpmailer/get_oauth_token.php
@@ -12,7 +12,7 @@
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
@@ -36,7 +36,7 @@
* Aliases for League Provider Classes
* Make sure you have added these to your composer.json and run `composer install`
* Plenty to choose from here:
- * @see http://oauth2-client.thephpleague.com/providers/thirdparty/
+ * @see https://oauth2-client.thephpleague.com/providers/thirdparty/
*/
//@see https://github.com/thephpleague/oauth2-google
use League\OAuth2\Client\Provider\Google;
@@ -178,5 +178,5 @@
);
//Use this to interact with an API on the users behalf
//Use this to get a new access token if the old one expires
- echo 'Refresh Token: ', $token->getRefreshToken();
+ echo 'Refresh Token: ', htmlspecialchars($token->getRefreshToken());
}
diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php
index 6992041873..4e74bfb7b8 100644
--- a/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php
+++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php
@@ -5,27 +5,32 @@
* @package PHPMailer
* @author Matt Sturdy
* @author Crystopher Glodzienski Cardoso
+ * @author Daniel Cruz
*/
$PHPMAILER_LANG['authenticate'] = 'Error SMTP: Imposible autentificar.';
+$PHPMAILER_LANG['buggy_php'] = 'Tu versión de PHP está afectada por un bug que puede resultar en mensajes corruptos. Para arreglarlo, cambia a enviar usando SMTP, deshabilita la opción mail.add_x_header en tu php.ini, cambia a MacOS o Linux, o actualiza tu PHP a la versión 7.0.17+ o 7.1.3+.';
$PHPMAILER_LANG['connect_host'] = 'Error SMTP: Imposible conectar al servidor SMTP.';
$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Datos no aceptados.';
$PHPMAILER_LANG['empty_message'] = 'El cuerpo del mensaje está vacío.';
$PHPMAILER_LANG['encoding'] = 'Codificación desconocida: ';
$PHPMAILER_LANG['execute'] = 'Imposible ejecutar: ';
+$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: ';
$PHPMAILER_LANG['file_access'] = 'Imposible acceder al archivo: ';
$PHPMAILER_LANG['file_open'] = 'Error de Archivo: Imposible abrir el archivo: ';
$PHPMAILER_LANG['from_failed'] = 'La(s) siguiente(s) direcciones de remitente fallaron: ';
$PHPMAILER_LANG['instantiate'] = 'Imposible crear una instancia de la función Mail.';
$PHPMAILER_LANG['invalid_address'] = 'Imposible enviar: dirección de email inválido: ';
+$PHPMAILER_LANG['invalid_header'] = 'Nombre o valor de encabezado no válido';
+$PHPMAILER_LANG['invalid_hostentry'] = 'Hostentry inválido: ';
+$PHPMAILER_LANG['invalid_host'] = 'Host inválido: ';
$PHPMAILER_LANG['mailer_not_supported'] = ' mailer no está soportado.';
$PHPMAILER_LANG['provide_address'] = 'Debe proporcionar al menos una dirección de email de destino.';
$PHPMAILER_LANG['recipients_failed'] = 'Error SMTP: Los siguientes destinos fallaron: ';
$PHPMAILER_LANG['signing'] = 'Error al firmar: ';
+$PHPMAILER_LANG['smtp_code'] = 'Código del servidor SMTP: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'Información adicional del servidor SMTP: ';
$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falló.';
+$PHPMAILER_LANG['smtp_detail'] = 'Detalle: ';
$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: ';
$PHPMAILER_LANG['variable_set'] = 'No se pudo configurar la variable: ';
-$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: ';
-$PHPMAILER_LANG['smtp_code'] = 'Código del servidor SMTP: ';
-$PHPMAILER_LANG['smtp_code_ex'] = 'Información adicional del servidor SMTP: ';
-$PHPMAILER_LANG['invalid_header'] = 'Nombre o valor de encabezado no válido';
diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php
index 0d367fcf88..a6d582d833 100644
--- a/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php
+++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php
@@ -6,7 +6,6 @@
* Some French punctuation requires a thin non-breaking space (U+202F) character before it,
* for example before a colon or exclamation mark.
* There is one of these characters between these quotes: " "
- * @see http://unicode.org/udhr/n/notes_fra.html
*/
$PHPMAILER_LANG['authenticate'] = 'Erreur SMTP : échec de l’authentification.';
@@ -31,7 +30,7 @@
$PHPMAILER_LANG['signing'] = 'Erreur de signature : ';
$PHPMAILER_LANG['smtp_code'] = 'Code SMTP : ';
$PHPMAILER_LANG['smtp_code_ex'] = 'Informations supplémentaires SMTP : ';
-$PHPMAILER_LANG['smtp_connect_failed'] = 'La fonction SMTP connect() a échouée.';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'La fonction SMTP connect() a échoué.';
$PHPMAILER_LANG['smtp_detail'] = 'Détails : ';
$PHPMAILER_LANG['smtp_error'] = 'Erreur du serveur SMTP : ';
$PHPMAILER_LANG['variable_set'] = 'Impossible d’initialiser ou de réinitialiser une variable : ';
diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php
index c76f5264c9..d01869cec8 100644
--- a/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php
+++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php
@@ -3,27 +3,35 @@
/**
* Japanese PHPMailer language file: refer to English translation for definitive list
* @package PHPMailer
- * @author Mitsuhiro Yoshida
+ * @author Mitsuhiro Yoshida
* @author Yoshi Sakai
* @author Arisophy
+ * @author ARAKI Musashi
*/
$PHPMAILER_LANG['authenticate'] = 'SMTPエラー: 認証できませんでした。';
+$PHPMAILER_LANG['buggy_php'] = 'ご利用のバージョンのPHPには不具合があり、メッセージが破損するおそれがあります。問題の解決は以下のいずれかを行ってください。SMTPでの送信に切り替える。php.iniのmail.add_x_headerをoffにする。MacOSまたはLinuxに切り替える。PHPバージョン7.0.17以降または7.1.3以降にアップグレードする。';
$PHPMAILER_LANG['connect_host'] = 'SMTPエラー: SMTPホストに接続できませんでした。';
$PHPMAILER_LANG['data_not_accepted'] = 'SMTPエラー: データが受け付けられませんでした。';
$PHPMAILER_LANG['empty_message'] = 'メール本文が空です。';
$PHPMAILER_LANG['encoding'] = '不明なエンコーディング: ';
$PHPMAILER_LANG['execute'] = '実行できませんでした: ';
+$PHPMAILER_LANG['extension_missing'] = '拡張機能が見つかりません: ';
$PHPMAILER_LANG['file_access'] = 'ファイルにアクセスできません: ';
$PHPMAILER_LANG['file_open'] = 'ファイルエラー: ファイルを開けません: ';
$PHPMAILER_LANG['from_failed'] = 'Fromアドレスを登録する際にエラーが発生しました: ';
$PHPMAILER_LANG['instantiate'] = 'メール関数が正常に動作しませんでした。';
$PHPMAILER_LANG['invalid_address'] = '不正なメールアドレス: ';
-$PHPMAILER_LANG['provide_address'] = '少なくとも1つメールアドレスを 指定する必要があります。';
+$PHPMAILER_LANG['invalid_header'] = '不正なヘッダー名またはその内容';
+$PHPMAILER_LANG['invalid_hostentry'] = '不正なホストエントリー: ';
+$PHPMAILER_LANG['invalid_host'] = '不正なホスト: ';
$PHPMAILER_LANG['mailer_not_supported'] = ' メーラーがサポートされていません。';
+$PHPMAILER_LANG['provide_address'] = '少なくとも1つメールアドレスを 指定する必要があります。';
$PHPMAILER_LANG['recipients_failed'] = 'SMTPエラー: 次の受信者アドレスに 間違いがあります: ';
$PHPMAILER_LANG['signing'] = '署名エラー: ';
+$PHPMAILER_LANG['smtp_code'] = 'SMTPコード: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'SMTP追加情報: ';
$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP接続に失敗しました。';
+$PHPMAILER_LANG['smtp_detail'] = '詳細: ';
$PHPMAILER_LANG['smtp_error'] = 'SMTPサーバーエラー: ';
$PHPMAILER_LANG['variable_set'] = '変数が存在しません: ';
-$PHPMAILER_LANG['extension_missing'] = '拡張機能が見つかりません: ';
diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php
new file mode 100644
index 0000000000..cf3bda69f2
--- /dev/null
+++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php
@@ -0,0 +1,27 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'هەڵەی SMTP : نەتوانرا کۆدەکە پشتڕاست بکرێتەوە ';
+$PHPMAILER_LANG['connect_host'] = 'هەڵەی SMTP: نەتوانرا پەیوەندی بە سێرڤەرەوە بکات SMTP.';
+$PHPMAILER_LANG['data_not_accepted'] = 'هەڵەی SMTP: ئەو زانیاریانە قبوڵ نەکرا.';
+$PHPMAILER_LANG['empty_message'] = 'پەیامەکە بەتاڵە';
+$PHPMAILER_LANG['encoding'] = 'کۆدکردنی نەزانراو : ';
+$PHPMAILER_LANG['execute'] = 'ناتوانرێت جێبەجێ بکرێت: ';
+$PHPMAILER_LANG['file_access'] = 'ناتوانرێت دەستت بگات بە فایلەکە: ';
+$PHPMAILER_LANG['file_open'] = 'هەڵەی پەڕگە(فایل): ناتوانرێت بکرێتەوە: ';
+$PHPMAILER_LANG['from_failed'] = 'هەڵە لە ئاستی ناونیشانی نێرەر: ';
+$PHPMAILER_LANG['instantiate'] = 'ناتوانرێت خزمەتگوزاری پۆستە پێشکەش بکرێت.';
+$PHPMAILER_LANG['invalid_address'] = 'نەتوانرا بنێردرێت ، چونکە ناونیشانی ئیمەیڵەکە نادروستە: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' مەیلەر پشتگیری ناکات';
+$PHPMAILER_LANG['provide_address'] = 'دەبێت ناونیشانی ئیمەیڵی لانیکەم یەک وەرگر دابین بکرێت.';
+$PHPMAILER_LANG['recipients_failed'] = ' هەڵەی SMTP: ئەم هەڵانەی خوارەوەشکستی هێنا لە ناردن بۆ هەردووکیان: ';
+$PHPMAILER_LANG['signing'] = 'هەڵەی واژۆ: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect()پەیوەندی شکستی هێنا .';
+$PHPMAILER_LANG['smtp_error'] = 'هەڵەی ئاستی سێرڤەری SMTP: ';
+$PHPMAILER_LANG['variable_set'] = 'ناتوانرێت بیگۆڕیت یان دوبارە بینێریتەوە: ';
+$PHPMAILER_LANG['extension_missing'] = 'درێژکراوە نەماوە: ';
diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php
index 8c8c5e8177..8013f37c4d 100644
--- a/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php
+++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php
@@ -5,24 +5,32 @@
* @package PHPMailer
* @author Alexey Chumakov
* @author Foster Snowhill
+ * @author ProjectSoft
*/
-$PHPMAILER_LANG['authenticate'] = 'Ошибка SMTP: ошибка авторизации.';
+$PHPMAILER_LANG['authenticate'] = 'Ошибка SMTP: не удалось пройти аутентификацию.';
+$PHPMAILER_LANG['buggy_php'] = 'В вашей версии PHP есть ошибка, которая может привести к повреждению сообщений. Чтобы исправить, переключитесь на отправку по SMTP, отключите опцию mail.add_x_header в ваш php.ini, переключитесь на MacOS или Linux или обновите PHP до версии 7.0.17+ или 7.1.3+.';
$PHPMAILER_LANG['connect_host'] = 'Ошибка SMTP: не удается подключиться к SMTP-серверу.';
$PHPMAILER_LANG['data_not_accepted'] = 'Ошибка SMTP: данные не приняты.';
+$PHPMAILER_LANG['empty_message'] = 'Пустое сообщение';
$PHPMAILER_LANG['encoding'] = 'Неизвестная кодировка: ';
$PHPMAILER_LANG['execute'] = 'Невозможно выполнить команду: ';
+$PHPMAILER_LANG['extension_missing'] = 'Расширение отсутствует: ';
$PHPMAILER_LANG['file_access'] = 'Нет доступа к файлу: ';
$PHPMAILER_LANG['file_open'] = 'Файловая ошибка: не удаётся открыть файл: ';
$PHPMAILER_LANG['from_failed'] = 'Неверный адрес отправителя: ';
$PHPMAILER_LANG['instantiate'] = 'Невозможно запустить функцию mail().';
-$PHPMAILER_LANG['provide_address'] = 'Пожалуйста, введите хотя бы один email-адрес получателя.';
-$PHPMAILER_LANG['mailer_not_supported'] = ' — почтовый сервер не поддерживается.';
-$PHPMAILER_LANG['recipients_failed'] = 'Ошибка SMTP: не удалась отправка таким адресатам: ';
-$PHPMAILER_LANG['empty_message'] = 'Пустое сообщение';
$PHPMAILER_LANG['invalid_address'] = 'Не отправлено из-за неправильного формата email-адреса: ';
+$PHPMAILER_LANG['invalid_header'] = 'Неверное имя или значение заголовка';
+$PHPMAILER_LANG['invalid_hostentry'] = 'Неверная запись хоста: ';
+$PHPMAILER_LANG['invalid_host'] = 'Неверный хост: ';
+$PHPMAILER_LANG['mailer_not_supported'] = ' — почтовый сервер не поддерживается.';
+$PHPMAILER_LANG['provide_address'] = 'Вы должны указать хотя бы один адрес электронной почты получателя.';
+$PHPMAILER_LANG['recipients_failed'] = 'Ошибка SMTP: Ошибка следующих получателей: ';
$PHPMAILER_LANG['signing'] = 'Ошибка подписи: ';
-$PHPMAILER_LANG['smtp_connect_failed'] = 'Ошибка соединения с SMTP-сервером';
+$PHPMAILER_LANG['smtp_code'] = 'Код SMTP: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'Дополнительная информация SMTP: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'Ошибка соединения с SMTP-сервером.';
+$PHPMAILER_LANG['smtp_detail'] = 'Детали: ';
$PHPMAILER_LANG['smtp_error'] = 'Ошибка SMTP-сервера: ';
$PHPMAILER_LANG['variable_set'] = 'Невозможно установить или сбросить переменную: ';
-$PHPMAILER_LANG['extension_missing'] = 'Расширение отсутствует: ';
diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php
index f938f8020e..3c45bc1c35 100644
--- a/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php
+++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php
@@ -11,21 +11,28 @@
*/
$PHPMAILER_LANG['authenticate'] = 'SMTP Hatası: Oturum açılamadı.';
+$PHPMAILER_LANG['buggy_php'] = 'PHP sürümünüz iletilerin bozulmasına neden olabilecek bir hatadan etkileniyor. Bunu düzeltmek için, SMTP kullanarak göndermeye geçin, mail.add_x_header seçeneğini devre dışı bırakın php.ini dosyanızdaki mail.add_x_header seçeneğini devre dışı bırakın, MacOS veya Linux geçin veya PHP sürümünü 7.0.17+ veya 7.1.3+ sürümüne yükseltin,';
$PHPMAILER_LANG['connect_host'] = 'SMTP Hatası: SMTP sunucusuna bağlanılamadı.';
$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Hatası: Veri kabul edilmedi.';
$PHPMAILER_LANG['empty_message'] = 'Mesajın içeriği boş';
$PHPMAILER_LANG['encoding'] = 'Bilinmeyen karakter kodlama: ';
$PHPMAILER_LANG['execute'] = 'Çalıştırılamadı: ';
+$PHPMAILER_LANG['extension_missing'] = 'Eklenti bulunamadı: ';
$PHPMAILER_LANG['file_access'] = 'Dosyaya erişilemedi: ';
$PHPMAILER_LANG['file_open'] = 'Dosya Hatası: Dosya açılamadı: ';
$PHPMAILER_LANG['from_failed'] = 'Belirtilen adreslere gönderme başarısız: ';
$PHPMAILER_LANG['instantiate'] = 'Örnek e-posta fonksiyonu oluşturulamadı.';
$PHPMAILER_LANG['invalid_address'] = 'Geçersiz e-posta adresi: ';
+$PHPMAILER_LANG['invalid_header'] = 'Geçersiz başlık adı veya değeri: ';
+$PHPMAILER_LANG['invalid_hostentry'] = 'Geçersiz ana bilgisayar girişi: ';
+$PHPMAILER_LANG['invalid_host'] = 'Geçersiz ana bilgisayar: ';
$PHPMAILER_LANG['mailer_not_supported'] = ' e-posta kütüphanesi desteklenmiyor.';
$PHPMAILER_LANG['provide_address'] = 'En az bir alıcı e-posta adresi belirtmelisiniz.';
$PHPMAILER_LANG['recipients_failed'] = 'SMTP Hatası: Belirtilen alıcılara ulaşılamadı: ';
$PHPMAILER_LANG['signing'] = 'İmzalama hatası: ';
+$PHPMAILER_LANG['smtp_code'] = 'SMTP kodu: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'ek SMTP bilgileri: ';
$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP connect() fonksiyonu başarısız.';
+$PHPMAILER_LANG['smtp_detail'] = 'SMTP SMTP Detayı: ';
$PHPMAILER_LANG['smtp_error'] = 'SMTP sunucu hatası: ';
$PHPMAILER_LANG['variable_set'] = 'Değişken ayarlanamadı ya da sıfırlanamadı: ';
-$PHPMAILER_LANG['extension_missing'] = 'Eklenti bulunamadı: ';
diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php
new file mode 100644
index 0000000000..0b9de0f127
--- /dev/null
+++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php
@@ -0,0 +1,30 @@
+
+ */
+
+$PHPMAILER_LANG['authenticate'] = 'SMTP خرابی: تصدیق کرنے سے قاصر۔';
+$PHPMAILER_LANG['connect_host'] = 'SMTP خرابی: سرور سے منسلک ہونے سے قاصر۔';
+$PHPMAILER_LANG['data_not_accepted'] = 'SMTP خرابی: ڈیٹا قبول نہیں کیا گیا۔';
+$PHPMAILER_LANG['empty_message'] = 'پیغام کی باڈی خالی ہے۔';
+$PHPMAILER_LANG['encoding'] = 'نامعلوم انکوڈنگ: ';
+$PHPMAILER_LANG['execute'] = 'عمل کرنے کے قابل نہیں ';
+$PHPMAILER_LANG['file_access'] = 'فائل تک رسائی سے قاصر:';
+$PHPMAILER_LANG['file_open'] = 'فائل کی خرابی: فائل کو کھولنے سے قاصر:';
+$PHPMAILER_LANG['from_failed'] = 'درج ذیل بھیجنے والے کا پتہ ناکام ہو گیا:';
+$PHPMAILER_LANG['instantiate'] = 'میل فنکشن کی مثال بنانے سے قاصر۔';
+$PHPMAILER_LANG['invalid_address'] = 'بھیجنے سے قاصر: غلط ای میل پتہ:';
+$PHPMAILER_LANG['mailer_not_supported'] = ' میلر تعاون یافتہ نہیں ہے۔';
+$PHPMAILER_LANG['provide_address'] = 'آپ کو کم از کم ایک منزل کا ای میل پتہ فراہم کرنا چاہیے۔';
+$PHPMAILER_LANG['recipients_failed'] = 'SMTP خرابی: درج ذیل پتہ پر نہیں بھیجا جاسکا: ';
+$PHPMAILER_LANG['signing'] = 'دستخط کی خرابی: ';
+$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP ملنا ناکام ہوا';
+$PHPMAILER_LANG['smtp_error'] = 'SMTP سرور کی خرابی: ';
+$PHPMAILER_LANG['variable_set'] = 'متغیر سیٹ نہیں کیا جا سکا: ';
+$PHPMAILER_LANG['extension_missing'] = 'ایکٹینشن موجود نہیں ہے۔ ';
+$PHPMAILER_LANG['smtp_code'] = 'SMTP سرور کوڈ: ';
+$PHPMAILER_LANG['smtp_code_ex'] = 'اضافی SMTP سرور کی معلومات:';
+$PHPMAILER_LANG['invalid_header'] = 'غلط ہیڈر کا نام یا قدر';
diff --git a/vendor/phpmailer/phpmailer/src/DSNConfigurator.php b/vendor/phpmailer/phpmailer/src/DSNConfigurator.php
index 566c9618f5..7058c1f05e 100644
--- a/vendor/phpmailer/phpmailer/src/DSNConfigurator.php
+++ b/vendor/phpmailer/phpmailer/src/DSNConfigurator.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2023 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/vendor/phpmailer/phpmailer/src/Exception.php b/vendor/phpmailer/phpmailer/src/Exception.php
index 52eaf95158..09c1a2cfef 100644
--- a/vendor/phpmailer/phpmailer/src/Exception.php
+++ b/vendor/phpmailer/phpmailer/src/Exception.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/vendor/phpmailer/phpmailer/src/OAuth.php b/vendor/phpmailer/phpmailer/src/OAuth.php
index c1d5b77623..a7e958860c 100644
--- a/vendor/phpmailer/phpmailer/src/OAuth.php
+++ b/vendor/phpmailer/phpmailer/src/OAuth.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
@@ -29,7 +29,7 @@
* OAuth - OAuth2 authentication wrapper class.
* Uses the oauth2-client package from the League of Extraordinary Packages.
*
- * @see http://oauth2-client.thephpleague.com
+ * @see https://oauth2-client.thephpleague.com
*
* @author Marcus Bointon (Synchro/coolbru)
*/
diff --git a/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php b/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php
index 1155507435..cbda1a1296 100644
--- a/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php
+++ b/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/vendor/phpmailer/phpmailer/src/PHPMailer.php b/vendor/phpmailer/phpmailer/src/PHPMailer.php
index ba4bcd4728..12da10354f 100644
--- a/vendor/phpmailer/phpmailer/src/PHPMailer.php
+++ b/vendor/phpmailer/phpmailer/src/PHPMailer.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
@@ -152,8 +152,7 @@ class PHPMailer
* Only supported in simple alt or alt_inline message types
* To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator.
*
- * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
- * @see http://kigkonsult.se/iCalcreator/
+ * @see https://kigkonsult.se/iCalcreator/
*
* @var string
*/
@@ -358,7 +357,7 @@ class PHPMailer
public $AuthType = '';
/**
- * SMTP SMTPXClient command attibutes
+ * SMTP SMTPXClient command attributes
*
* @var array
*/
@@ -468,7 +467,7 @@ class PHPMailer
* Only applicable when sending via SMTP.
*
* @see https://en.wikipedia.org/wiki/Variable_envelope_return_path
- * @see http://www.postfix.org/VERP_README.html Postfix VERP info
+ * @see https://www.postfix.org/VERP_README.html Postfix VERP info
*
* @var bool
*/
@@ -551,10 +550,10 @@ class PHPMailer
* The function that handles the result of the send email action.
* It is called out by send() for each email sent.
*
- * Value can be any php callable: http://www.php.net/is_callable
+ * Value can be any php callable: https://www.php.net/is_callable
*
* Parameters:
- * bool $result result of the send action
+ * bool $result result of the send action
* array $to email addresses of the recipients
* array $cc cc email addresses
* array $bcc bcc email addresses
@@ -757,7 +756,7 @@ class PHPMailer
*
* @var string
*/
- const VERSION = '6.9.1';
+ const VERSION = '6.9.2';
/**
* Error severity: message only, continue processing.
@@ -903,7 +902,7 @@ protected function edebug($str)
}
//Is this a PSR-3 logger?
if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
- $this->Debugoutput->debug($str);
+ $this->Debugoutput->debug(rtrim($str, "\r\n"));
return;
}
@@ -1072,7 +1071,7 @@ public function addReplyTo($address, $name = '')
* be modified after calling this function), addition of such addresses is delayed until send().
* Addresses that have been added already return false, but do not throw exceptions.
*
- * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
+ * @param string $kind One of 'to', 'cc', 'bcc', or 'Reply-To'
* @param string $address The email address
* @param string $name An optional username associated with the address
*
@@ -1212,7 +1211,7 @@ protected function addAnAddress($kind, $address, $name = '')
* Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
* Note that quotes in the name part are removed.
*
- * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
+ * @see https://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
*
* @param string $addrstr The address list string
* @param bool $useimap Whether to use the IMAP extension to parse the list
@@ -1407,7 +1406,6 @@ public static function validateAddress($address, $patternselect = null)
* * IPv6 literals: 'first.last@[IPv6:a1::]'
* Not all of these will necessarily work for sending!
*
- * @see http://squiloople.com/2009/12/20/email-address-validation/
* @copyright 2009-2010 Michael Rushton
* Feel free to use and redistribute this code. But please keep this copyright notice.
*/
@@ -1734,9 +1732,8 @@ protected function sendmailSend($header, $body)
//This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
//A space after `-f` is optional, but there is a long history of its presence
//causing problems, so we don't use one
- //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
- //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html
- //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html
+ //Exim docs: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
+ //Sendmail docs: https://www.sendmail.org/~ca/email/man/sendmail.html
//Example problem: https://www.drupal.org/node/1057954
//PHP 5.6 workaround
@@ -1901,7 +1898,7 @@ protected static function fileIsAccessible($path)
/**
* Send mail using the PHP mail() function.
*
- * @see http://www.php.net/manual/en/book.mail.php
+ * @see https://www.php.net/manual/en/book.mail.php
*
* @param string $header The message headers
* @param string $body The message body
@@ -1931,9 +1928,8 @@ protected function mailSend($header, $body)
//This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
//A space after `-f` is optional, but there is a long history of its presence
//causing problems, so we don't use one
- //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
- //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html
- //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html
+ //Exim docs: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
+ //Sendmail docs: https://www.sendmail.org/~ca/email/man/sendmail.html
//Example problem: https://www.drupal.org/node/1057954
//CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
@@ -3634,7 +3630,7 @@ public function has8bitChars($text)
* without breaking lines within a character.
* Adapted from a function by paravoid.
*
- * @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
+ * @see https://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
*
* @param string $str multi-byte text to wrap encode
* @param string $linebreak string to use as linefeed/end-of-line
@@ -3690,7 +3686,7 @@ public function encodeQP($string)
/**
* Encode a string using Q encoding.
*
- * @see http://tools.ietf.org/html/rfc2047#section-4.2
+ * @see https://www.rfc-editor.org/rfc/rfc2047#section-4.2
*
* @param string $str the text to encode
* @param string $position Where the text is going to be used, see the RFC for what that means
@@ -4228,7 +4224,7 @@ protected function serverHostname()
$result = $_SERVER['SERVER_NAME'];
} elseif (function_exists('gethostname') && gethostname() !== false) {
$result = gethostname();
- } elseif (php_uname('n') !== false) {
+ } elseif (php_uname('n') !== '') {
$result = php_uname('n');
}
if (!static::isValidHost($result)) {
@@ -4253,7 +4249,7 @@ public static function isValidHost($host)
empty($host)
|| !is_string($host)
|| strlen($host) > 256
- || !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+\])$/', $host)
+ || !preg_match('/^([a-z\d.-]*|\[[a-f\d:]+\])$/i', $host)
) {
return false;
}
@@ -4267,8 +4263,8 @@ public static function isValidHost($host)
//Is it a valid IPv4 address?
return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
}
- //Is it a syntactically valid hostname (when embeded in a URL)?
- return filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false;
+ //Is it a syntactically valid hostname (when embedded in a URL)?
+ return filter_var('https://' . $host, FILTER_VALIDATE_URL) !== false;
}
/**
@@ -4679,7 +4675,7 @@ public static function filenameToType($filename)
* Multi-byte-safe pathinfo replacement.
* Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe.
*
- * @see http://www.php.net/manual/en/function.pathinfo.php#107461
+ * @see https://www.php.net/manual/en/function.pathinfo.php#107461
*
* @param string $path A filename or path, does not need to exist as a file
* @param int|string $options Either a PATHINFO_* constant,
diff --git a/vendor/phpmailer/phpmailer/src/POP3.php b/vendor/phpmailer/phpmailer/src/POP3.php
index 7b25fdd7e8..697c96126f 100644
--- a/vendor/phpmailer/phpmailer/src/POP3.php
+++ b/vendor/phpmailer/phpmailer/src/POP3.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
@@ -46,7 +46,7 @@ class POP3
*
* @var string
*/
- const VERSION = '6.9.1';
+ const VERSION = '6.9.2';
/**
* Default POP3 port number.
@@ -250,7 +250,9 @@ public function connect($host, $port = false, $tval = 30)
//On Windows this will raise a PHP Warning error if the hostname doesn't exist.
//Rather than suppress it with @fsockopen, capture it cleanly instead
- set_error_handler([$this, 'catchWarning']);
+ set_error_handler(function () {
+ call_user_func_array([$this, 'catchWarning'], func_get_args());
+ });
if (false === $port) {
$port = static::DEFAULT_PORT;
diff --git a/vendor/phpmailer/phpmailer/src/SMTP.php b/vendor/phpmailer/phpmailer/src/SMTP.php
index 1b5b00771c..5b238b5279 100644
--- a/vendor/phpmailer/phpmailer/src/SMTP.php
+++ b/vendor/phpmailer/phpmailer/src/SMTP.php
@@ -13,7 +13,7 @@
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
@@ -35,7 +35,7 @@ class SMTP
*
* @var string
*/
- const VERSION = '6.9.1';
+ const VERSION = '6.9.2';
/**
* SMTP line break constant.
@@ -152,8 +152,8 @@ class SMTP
/**
* Whether to use VERP.
*
- * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path
- * @see http://www.postfix.org/VERP_README.html Info on VERP
+ * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path
+ * @see https://www.postfix.org/VERP_README.html Info on VERP
*
* @var bool
*/
@@ -164,7 +164,7 @@ class SMTP
* Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
* This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
*
- * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2
+ * @see https://www.rfc-editor.org/rfc/rfc2821#section-4.5.3.2
*
* @var int
*/
@@ -187,12 +187,12 @@ class SMTP
*/
protected $smtp_transaction_id_patterns = [
'exim' => '/[\d]{3} OK id=(.*)/',
- 'sendmail' => '/[\d]{3} 2.0.0 (.*) Message/',
- 'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/',
- 'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/',
+ 'sendmail' => '/[\d]{3} 2\.0\.0 (.*) Message/',
+ 'postfix' => '/[\d]{3} 2\.0\.0 Ok: queued as (.*)/',
+ 'Microsoft_ESMTP' => '/[0-9]{3} 2\.[\d]\.0 (.*)@(?:.*) Queued mail for delivery/',
'Amazon_SES' => '/[\d]{3} Ok (.*)/',
'SendGrid' => '/[\d]{3} Ok: queued as (.*)/',
- 'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/',
+ 'CampaignMonitor' => '/[\d]{3} 2\.0\.0 OK:([a-zA-Z\d]{48})/',
'Haraka' => '/[\d]{3} Message Queued \((.*)\)/',
'ZoneMTA' => '/[\d]{3} Message queued as (.*)/',
'Mailjet' => '/[\d]{3} OK queued as (.*)/',
@@ -280,7 +280,8 @@ protected function edebug($str, $level = 0)
}
//Is this a PSR-3 logger?
if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
- $this->Debugoutput->debug($str);
+ //Remove trailing line breaks potentially added by calls to SMTP::client_send()
+ $this->Debugoutput->debug(rtrim($str, "\r\n"));
return;
}
@@ -293,6 +294,7 @@ protected function edebug($str, $level = 0)
switch ($this->Debugoutput) {
case 'error_log':
//Don't output, just log
+ /** @noinspection ForgottenDebugOutputInspection */
error_log($str);
break;
case 'html':
@@ -404,7 +406,9 @@ protected function getSMTPConnection($host, $port = null, $timeout = 30, $option
$errstr = '';
if ($streamok) {
$socket_context = stream_context_create($options);
- set_error_handler([$this, 'errorHandler']);
+ set_error_handler(function () {
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
+ });
$connection = stream_socket_client(
$host . ':' . $port,
$errno,
@@ -419,7 +423,9 @@ protected function getSMTPConnection($host, $port = null, $timeout = 30, $option
'Connection: stream_socket_client not available, falling back to fsockopen',
self::DEBUG_CONNECTION
);
- set_error_handler([$this, 'errorHandler']);
+ set_error_handler(function () {
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
+ });
$connection = fsockopen(
$host,
$port,
@@ -483,7 +489,9 @@ public function startTLS()
}
//Begin encrypted connection
- set_error_handler([$this, 'errorHandler']);
+ set_error_handler(function () {
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
+ });
$crypto_ok = stream_socket_enable_crypto(
$this->smtp_conn,
true,
@@ -648,7 +656,7 @@ protected function hmac($data, $key)
}
//The following borrowed from
- //http://php.net/manual/en/function.mhash.php#27225
+ //https://www.php.net/manual/en/function.mhash.php#27225
//RFC 2104 HMAC implementation for php.
//Creates an md5 HMAC.
@@ -1162,7 +1170,9 @@ public function client_send($data, $command = '')
} else {
$this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
}
- set_error_handler([$this, 'errorHandler']);
+ set_error_handler(function () {
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
+ });
$result = fwrite($this->smtp_conn, $data);
restore_error_handler();
@@ -1265,7 +1275,9 @@ protected function get_lines()
while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
//Must pass vars in here as params are by reference
//solution for signals inspired by https://github.com/symfony/symfony/pull/6540
- set_error_handler([$this, 'errorHandler']);
+ set_error_handler(function () {
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
+ });
$n = stream_select($selR, $selW, $selW, $this->Timelimit);
restore_error_handler();