Update Kirby and dependencies
This commit is contained in:
parent
503b339974
commit
399fa20902
439 changed files with 66915 additions and 64442 deletions
|
@ -16,24 +16,24 @@ namespace Kirby\Panel;
|
|||
*/
|
||||
class Dialog extends Json
|
||||
{
|
||||
protected static $key = '$dialog';
|
||||
protected static $key = '$dialog';
|
||||
|
||||
/**
|
||||
* Renders dialogs
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param array $options
|
||||
* @return \Kirby\Http\Response
|
||||
*/
|
||||
public static function response($data, array $options = [])
|
||||
{
|
||||
// interpret true as success
|
||||
if ($data === true) {
|
||||
$data = [
|
||||
'code' => 200
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Renders dialogs
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param array $options
|
||||
* @return \Kirby\Http\Response
|
||||
*/
|
||||
public static function response($data, array $options = [])
|
||||
{
|
||||
// interpret true as success
|
||||
if ($data === true) {
|
||||
$data = [
|
||||
'code' => 200
|
||||
];
|
||||
}
|
||||
|
||||
return parent::response($data, $options);
|
||||
}
|
||||
return parent::response($data, $options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
namespace Kirby\Panel;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Helpers;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Filesystem\Asset;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Response;
|
||||
|
@ -25,272 +28,278 @@ use Throwable;
|
|||
*/
|
||||
class Document
|
||||
{
|
||||
/**
|
||||
* Generates an array with all assets
|
||||
* that need to be loaded for the panel (js, css, icons)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function assets(): array
|
||||
{
|
||||
$kirby = kirby();
|
||||
$nonce = $kirby->nonce();
|
||||
/**
|
||||
* Generates an array with all assets
|
||||
* that need to be loaded for the panel (js, css, icons)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function assets(): array
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$nonce = $kirby->nonce();
|
||||
|
||||
// get the assets from the Vite dev server in dev mode;
|
||||
// dev mode = explicitly enabled in the config AND Vite is running
|
||||
$dev = $kirby->option('panel.dev', false);
|
||||
$isDev = $dev !== false && is_file($kirby->roots()->panel() . '/.vite-running') === true;
|
||||
// get the assets from the Vite dev server in dev mode;
|
||||
// dev mode = explicitly enabled in the config AND Vite is running
|
||||
$dev = $kirby->option('panel.dev', false);
|
||||
$isDev = $dev !== false && is_file($kirby->roots()->panel() . '/.vite-running') === true;
|
||||
|
||||
if ($isDev === true) {
|
||||
// vite on explicitly configured base URL or port 3000
|
||||
// of the current Kirby request
|
||||
if (is_string($dev) === true) {
|
||||
$url = $dev;
|
||||
} else {
|
||||
$url = rtrim($kirby->request()->url([
|
||||
'port' => 3000,
|
||||
'path' => null,
|
||||
'params' => null,
|
||||
'query' => null
|
||||
])->toString(), '/');
|
||||
}
|
||||
} else {
|
||||
// vite is not running, use production assets
|
||||
$url = $kirby->url('media') . '/panel/' . $kirby->versionHash();
|
||||
}
|
||||
if ($isDev === true) {
|
||||
// vite on explicitly configured base URL or port 3000
|
||||
// of the current Kirby request
|
||||
if (is_string($dev) === true) {
|
||||
$url = $dev;
|
||||
} else {
|
||||
$url = rtrim($kirby->request()->url([
|
||||
'port' => 3000,
|
||||
'path' => null,
|
||||
'params' => null,
|
||||
'query' => null
|
||||
])->toString(), '/');
|
||||
}
|
||||
} else {
|
||||
// vite is not running, use production assets
|
||||
$url = $kirby->url('media') . '/panel/' . $kirby->versionHash();
|
||||
}
|
||||
|
||||
// fetch all plugins
|
||||
$plugins = new Plugins();
|
||||
// fetch all plugins
|
||||
$plugins = new Plugins();
|
||||
|
||||
$assets = [
|
||||
'css' => [
|
||||
'index' => $url . '/css/style.css',
|
||||
'plugins' => $plugins->url('css'),
|
||||
'custom' => static::customAsset('panel.css'),
|
||||
],
|
||||
'icons' => static::favicon($url),
|
||||
'js' => [
|
||||
'vendor' => [
|
||||
'nonce' => $nonce,
|
||||
'src' => $url . '/js/vendor.js',
|
||||
'type' => 'module'
|
||||
],
|
||||
'pluginloader' => [
|
||||
'nonce' => $nonce,
|
||||
'src' => $url . '/js/plugins.js',
|
||||
'type' => 'module'
|
||||
],
|
||||
'plugins' => [
|
||||
'nonce' => $nonce,
|
||||
'src' => $plugins->url('js'),
|
||||
'defer' => true
|
||||
],
|
||||
'custom' => [
|
||||
'nonce' => $nonce,
|
||||
'src' => static::customAsset('panel.js'),
|
||||
'type' => 'module'
|
||||
],
|
||||
'index' => [
|
||||
'nonce' => $nonce,
|
||||
'src' => $url . '/js/index.js',
|
||||
'type' => 'module'
|
||||
],
|
||||
]
|
||||
];
|
||||
$assets = [
|
||||
'css' => [
|
||||
'index' => $url . '/css/style.css',
|
||||
'plugins' => $plugins->url('css'),
|
||||
'custom' => static::customAsset('panel.css'),
|
||||
],
|
||||
'icons' => static::favicon($url),
|
||||
// loader for plugins' index.dev.mjs files – inlined, so we provide the code instead of the asset URL
|
||||
'plugin-imports' => $plugins->read('mjs'),
|
||||
'js' => [
|
||||
'vendor' => [
|
||||
'nonce' => $nonce,
|
||||
'src' => $url . '/js/vendor.js',
|
||||
'type' => 'module'
|
||||
],
|
||||
'pluginloader' => [
|
||||
'nonce' => $nonce,
|
||||
'src' => $url . '/js/plugins.js',
|
||||
'type' => 'module'
|
||||
],
|
||||
'plugins' => [
|
||||
'nonce' => $nonce,
|
||||
'src' => $plugins->url('js'),
|
||||
'defer' => true
|
||||
],
|
||||
'custom' => [
|
||||
'nonce' => $nonce,
|
||||
'src' => static::customAsset('panel.js'),
|
||||
'type' => 'module'
|
||||
],
|
||||
'index' => [
|
||||
'nonce' => $nonce,
|
||||
'src' => $url . '/js/index.js',
|
||||
'type' => 'module'
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
// during dev mode, add vite client and adapt
|
||||
// path to `index.js` - vendor and stylesheet
|
||||
// don't need to be loaded in dev mode
|
||||
if ($isDev === true) {
|
||||
$assets['js']['vite'] = [
|
||||
'nonce' => $nonce,
|
||||
'src' => $url . '/@vite/client',
|
||||
'type' => 'module'
|
||||
];
|
||||
$assets['js']['index'] = [
|
||||
'nonce' => $nonce,
|
||||
'src' => $url . '/src/index.js',
|
||||
'type' => 'module'
|
||||
];
|
||||
// during dev mode, add vite client and adapt
|
||||
// path to `index.js` - vendor and stylesheet
|
||||
// don't need to be loaded in dev mode
|
||||
if ($isDev === true) {
|
||||
$assets['js']['vite'] = [
|
||||
'nonce' => $nonce,
|
||||
'src' => $url . '/@vite/client',
|
||||
'type' => 'module'
|
||||
];
|
||||
$assets['js']['index'] = [
|
||||
'nonce' => $nonce,
|
||||
'src' => $url . '/src/index.js',
|
||||
'type' => 'module'
|
||||
];
|
||||
|
||||
unset($assets['css']['index'], $assets['js']['vendor']);
|
||||
}
|
||||
unset($assets['css']['index'], $assets['js']['vendor']);
|
||||
}
|
||||
|
||||
// remove missing files
|
||||
$assets['css'] = array_filter($assets['css']);
|
||||
$assets['js'] = array_filter(
|
||||
$assets['js'],
|
||||
fn ($js) => empty($js['src']) === false
|
||||
);
|
||||
// remove missing files
|
||||
$assets['css'] = array_filter($assets['css']);
|
||||
$assets['js'] = array_filter(
|
||||
$assets['js'],
|
||||
fn ($js) => empty($js['src']) === false
|
||||
);
|
||||
|
||||
return $assets;
|
||||
}
|
||||
return $assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a custom asset file from the
|
||||
* config (e.g. panel.css or panel.js)
|
||||
* @since 3.6.2
|
||||
*
|
||||
* @param string $option asset option name
|
||||
* @return string|null
|
||||
*/
|
||||
public static function customAsset(string $option): ?string
|
||||
{
|
||||
if ($path = kirby()->option($option)) {
|
||||
$asset = asset($path);
|
||||
/**
|
||||
* Check for a custom asset file from the
|
||||
* config (e.g. panel.css or panel.js)
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @param string $option asset option name
|
||||
* @return string|null
|
||||
*/
|
||||
public static function customAsset(string $option): ?string
|
||||
{
|
||||
if ($path = App::instance()->option($option)) {
|
||||
$asset = new Asset($path);
|
||||
|
||||
if ($asset->exists() === true) {
|
||||
return $asset->url() . '?' . $asset->modified();
|
||||
}
|
||||
}
|
||||
if ($asset->exists() === true) {
|
||||
return $asset->url() . '?' . $asset->modified();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 3.7.0 Use `Document::customAsset('panel.css)` instead
|
||||
* @todo add deprecation warning in 3.7.0, remove in 3.8.0
|
||||
*/
|
||||
public static function customCss(): ?string
|
||||
{
|
||||
return static::customAsset('panel.css');
|
||||
}
|
||||
/**
|
||||
* @deprecated 3.7.0 Use `Document::customAsset('panel.css)` instead
|
||||
* @todo remove in 3.8.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function customCss(): ?string
|
||||
{
|
||||
Helpers::deprecated('Panel\Document::customCss() has been deprecated and will be removed in Kirby 3.8.0. Use Panel\Document::customAsset(\'panel.css\') instead.');
|
||||
return static::customAsset('panel.css');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 3.7.0 Use `Document::customAsset('panel.js)` instead
|
||||
* @todo add deprecation warning in 3.7.0, remove in 3.8.0
|
||||
*/
|
||||
public static function customJs(): ?string
|
||||
{
|
||||
return static::customAsset('panel.js');
|
||||
}
|
||||
/**
|
||||
* @deprecated 3.7.0 Use `Document::customAsset('panel.js)` instead
|
||||
* @todo remove in 3.8.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function customJs(): ?string
|
||||
{
|
||||
Helpers::deprecated('Panel\Document::customJs() has been deprecated and will be removed in Kirby 3.8.0. Use Panel\Document::customAsset(\'panel.js\') instead.');
|
||||
return static::customAsset('panel.js');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of favion icons
|
||||
* based on config option
|
||||
* @since 3.6.2
|
||||
*
|
||||
* @param string $url URL prefix for default icons
|
||||
* @return array
|
||||
*/
|
||||
public static function favicon(string $url = ''): array
|
||||
{
|
||||
$kirby = kirby();
|
||||
$icons = $kirby->option('panel.favicon', [
|
||||
'apple-touch-icon' => [
|
||||
'type' => 'image/png',
|
||||
'url' => $url . '/apple-touch-icon.png',
|
||||
],
|
||||
'shortcut icon' => [
|
||||
'type' => 'image/svg+xml',
|
||||
'url' => $url . '/favicon.svg',
|
||||
],
|
||||
'alternate icon' => [
|
||||
'type' => 'image/png',
|
||||
'url' => $url . '/favicon.png',
|
||||
]
|
||||
]);
|
||||
/**
|
||||
* Returns array of favion icons
|
||||
* based on config option
|
||||
* @since 3.7.0
|
||||
*
|
||||
* @param string $url URL prefix for default icons
|
||||
* @return array
|
||||
*/
|
||||
public static function favicon(string $url = ''): array
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$icons = $kirby->option('panel.favicon', [
|
||||
'apple-touch-icon' => [
|
||||
'type' => 'image/png',
|
||||
'url' => $url . '/apple-touch-icon.png',
|
||||
],
|
||||
'shortcut icon' => [
|
||||
'type' => 'image/svg+xml',
|
||||
'url' => $url . '/favicon.svg',
|
||||
],
|
||||
'alternate icon' => [
|
||||
'type' => 'image/png',
|
||||
'url' => $url . '/favicon.png',
|
||||
]
|
||||
]);
|
||||
|
||||
if (is_array($icons) === true) {
|
||||
return $icons;
|
||||
}
|
||||
if (is_array($icons) === true) {
|
||||
return $icons;
|
||||
}
|
||||
|
||||
// make sure to convert favicon string to array
|
||||
if (is_string($icons) === true) {
|
||||
return [
|
||||
'shortcut icon' => [
|
||||
'type' => F::mime($icons),
|
||||
'url' => $icons,
|
||||
]
|
||||
];
|
||||
}
|
||||
// make sure to convert favicon string to array
|
||||
if (is_string($icons) === true) {
|
||||
return [
|
||||
'shortcut icon' => [
|
||||
'type' => F::mime($icons),
|
||||
'url' => $icons,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Invalid panel.favicon option');
|
||||
}
|
||||
throw new InvalidArgumentException('Invalid panel.favicon option');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the SVG icon sprite
|
||||
* This will be injected in the
|
||||
* initial HTML document for the Panel
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function icons(): string
|
||||
{
|
||||
return F::read(kirby()->root('kirby') . '/panel/dist/img/icons.svg');
|
||||
}
|
||||
/**
|
||||
* Load the SVG icon sprite
|
||||
* This will be injected in the
|
||||
* initial HTML document for the Panel
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function icons(): string
|
||||
{
|
||||
return F::read(App::instance()->root('kirby') . '/panel/dist/img/icons.svg');
|
||||
}
|
||||
|
||||
/**
|
||||
* Links all dist files in the media folder
|
||||
* and returns the link to the requested asset
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\Exception If Panel assets could not be moved to the public directory
|
||||
*/
|
||||
public static function link(): bool
|
||||
{
|
||||
$kirby = kirby();
|
||||
$mediaRoot = $kirby->root('media') . '/panel';
|
||||
$panelRoot = $kirby->root('panel') . '/dist';
|
||||
$versionHash = $kirby->versionHash();
|
||||
$versionRoot = $mediaRoot . '/' . $versionHash;
|
||||
/**
|
||||
* Links all dist files in the media folder
|
||||
* and returns the link to the requested asset
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\Exception If Panel assets could not be moved to the public directory
|
||||
*/
|
||||
public static function link(): bool
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$mediaRoot = $kirby->root('media') . '/panel';
|
||||
$panelRoot = $kirby->root('panel') . '/dist';
|
||||
$versionHash = $kirby->versionHash();
|
||||
$versionRoot = $mediaRoot . '/' . $versionHash;
|
||||
|
||||
// check if the version already exists
|
||||
if (is_dir($versionRoot) === true) {
|
||||
return false;
|
||||
}
|
||||
// check if the version already exists
|
||||
if (is_dir($versionRoot) === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// delete the panel folder and all previous versions
|
||||
Dir::remove($mediaRoot);
|
||||
// delete the panel folder and all previous versions
|
||||
Dir::remove($mediaRoot);
|
||||
|
||||
// recreate the panel folder
|
||||
Dir::make($mediaRoot, true);
|
||||
// recreate the panel folder
|
||||
Dir::make($mediaRoot, true);
|
||||
|
||||
// copy assets to the dist folder
|
||||
if (Dir::copy($panelRoot, $versionRoot) !== true) {
|
||||
throw new Exception('Panel assets could not be linked');
|
||||
}
|
||||
// copy assets to the dist folder
|
||||
if (Dir::copy($panelRoot, $versionRoot) !== true) {
|
||||
throw new Exception('Panel assets could not be linked');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the panel document
|
||||
*
|
||||
* @param array $fiber
|
||||
* @return \Kirby\Http\Response
|
||||
*/
|
||||
public static function response(array $fiber)
|
||||
{
|
||||
$kirby = kirby();
|
||||
/**
|
||||
* Renders the panel document
|
||||
*
|
||||
* @param array $fiber
|
||||
* @return \Kirby\Http\Response
|
||||
*/
|
||||
public static function response(array $fiber)
|
||||
{
|
||||
$kirby = App::instance();
|
||||
|
||||
// Full HTML response
|
||||
// @codeCoverageIgnoreStart
|
||||
try {
|
||||
if (static::link() === true) {
|
||||
usleep(1);
|
||||
go($kirby->url('index') . '/' . $kirby->path());
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
die('The Panel assets cannot be installed properly. ' . $e->getMessage());
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
// Full HTML response
|
||||
// @codeCoverageIgnoreStart
|
||||
try {
|
||||
if (static::link() === true) {
|
||||
usleep(1);
|
||||
Response::go($kirby->url('base') . '/' . $kirby->path());
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
die('The Panel assets cannot be installed properly. ' . $e->getMessage());
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
// get the uri object for the panel url
|
||||
$uri = new Uri($url = $kirby->url('panel'));
|
||||
// get the uri object for the panel url
|
||||
$uri = new Uri($url = $kirby->url('panel'));
|
||||
|
||||
// proper response code
|
||||
$code = $fiber['$view']['code'] ?? 200;
|
||||
// proper response code
|
||||
$code = $fiber['$view']['code'] ?? 200;
|
||||
|
||||
// load the main Panel view template
|
||||
$body = Tpl::load($kirby->root('kirby') . '/views/panel.php', [
|
||||
'assets' => static::assets(),
|
||||
'icons' => static::icons(),
|
||||
'nonce' => $kirby->nonce(),
|
||||
'fiber' => $fiber,
|
||||
'panelUrl' => $uri->path()->toString(true) . '/',
|
||||
]);
|
||||
// load the main Panel view template
|
||||
$body = Tpl::load($kirby->root('kirby') . '/views/panel.php', [
|
||||
'assets' => static::assets(),
|
||||
'icons' => static::icons(),
|
||||
'nonce' => $kirby->nonce(),
|
||||
'fiber' => $fiber,
|
||||
'panelUrl' => $uri->path()->toString(true) . '/',
|
||||
]);
|
||||
|
||||
return new Response($body, 'text/html', $code);
|
||||
}
|
||||
return new Response($body, 'text/html', $code);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Kirby\Panel;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\Find;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Http\Uri;
|
||||
|
@ -22,68 +23,68 @@ use Throwable;
|
|||
*/
|
||||
class Dropdown extends Json
|
||||
{
|
||||
protected static $key = '$dropdown';
|
||||
protected static $key = '$dropdown';
|
||||
|
||||
/**
|
||||
* Returns the options for the changes dropdown
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function changes(): array
|
||||
{
|
||||
$kirby = kirby();
|
||||
$multilang = $kirby->multilang();
|
||||
$ids = Str::split(get('ids'));
|
||||
$options = [];
|
||||
/**
|
||||
* Returns the options for the changes dropdown
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function changes(): array
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$multilang = $kirby->multilang();
|
||||
$ids = Str::split($kirby->request()->get('ids'));
|
||||
$options = [];
|
||||
|
||||
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();
|
||||
$option = Find::parent($path)->panel()->dropdownOption();
|
||||
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();
|
||||
$option = Find::parent($path)->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)) {
|
||||
$option['text'] .= ' (' . $language->code() . ')';
|
||||
$option['link'] .= '?language=' . $language->code();
|
||||
}
|
||||
// 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)) {
|
||||
$option['text'] .= ' (' . $language->code() . ')';
|
||||
$option['link'] .= '?language=' . $language->code();
|
||||
}
|
||||
|
||||
$options[] = $option;
|
||||
} catch (Throwable $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$options[] = $option;
|
||||
} catch (Throwable $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// the given set of ids does not match any
|
||||
// real models. This means that the stored ids
|
||||
// in local storage are not correct and the changes
|
||||
// store needs to be cleared
|
||||
if (empty($options) === true) {
|
||||
throw new LogicException('No changes for given models');
|
||||
}
|
||||
// the given set of ids does not match any
|
||||
// real models. This means that the stored ids
|
||||
// in local storage are not correct and the changes
|
||||
// store needs to be cleared
|
||||
if (empty($options) === true) {
|
||||
throw new LogicException('No changes for given models');
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders dropdowns
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param array $options
|
||||
* @return \Kirby\Http\Response
|
||||
*/
|
||||
public static function response($data, array $options = [])
|
||||
{
|
||||
if (is_array($data) === true) {
|
||||
$data = [
|
||||
'options' => array_values($data)
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Renders dropdowns
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param array $options
|
||||
* @return \Kirby\Http\Response
|
||||
*/
|
||||
public static function response($data, array $options = [])
|
||||
{
|
||||
if (is_array($data) === true) {
|
||||
$data = [
|
||||
'options' => array_values($data)
|
||||
];
|
||||
}
|
||||
|
||||
return parent::response($data, $options);
|
||||
}
|
||||
return parent::response($data, $options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
namespace Kirby\Panel;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\File;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
/**
|
||||
* Provides common field prop definitions
|
||||
|
@ -18,255 +20,255 @@ use Kirby\Cms\Page;
|
|||
*/
|
||||
class Field
|
||||
{
|
||||
/**
|
||||
* A standard email field
|
||||
*
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function email(array $props = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'label' => t('email'),
|
||||
'type' => 'email',
|
||||
'counter' => false,
|
||||
], $props);
|
||||
}
|
||||
/**
|
||||
* A standard email field
|
||||
*
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function email(array $props = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'label' => I18n::translate('email'),
|
||||
'type' => 'email',
|
||||
'counter' => false,
|
||||
], $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* File position
|
||||
*
|
||||
* @param \Kirby\Cms\File
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function filePosition(File $file, array $props = []): array
|
||||
{
|
||||
$index = 0;
|
||||
$options = [];
|
||||
/**
|
||||
* File position
|
||||
*
|
||||
* @param \Kirby\Cms\File
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function filePosition(File $file, array $props = []): array
|
||||
{
|
||||
$index = 0;
|
||||
$options = [];
|
||||
|
||||
foreach ($file->siblings(false)->sorted() as $sibling) {
|
||||
$index++;
|
||||
foreach ($file->siblings(false)->sorted() as $sibling) {
|
||||
$index++;
|
||||
|
||||
$options[] = [
|
||||
'value' => $index,
|
||||
'text' => $index
|
||||
];
|
||||
$options[] = [
|
||||
'value' => $index,
|
||||
'text' => $index
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'value' => $sibling->id(),
|
||||
'text' => $sibling->filename(),
|
||||
'disabled' => true
|
||||
];
|
||||
}
|
||||
$options[] = [
|
||||
'value' => $sibling->id(),
|
||||
'text' => $sibling->filename(),
|
||||
'disabled' => true
|
||||
];
|
||||
}
|
||||
|
||||
$index++;
|
||||
$index++;
|
||||
|
||||
$options[] = [
|
||||
'value' => $index,
|
||||
'text' => $index
|
||||
];
|
||||
$options[] = [
|
||||
'value' => $index,
|
||||
'text' => $index
|
||||
];
|
||||
|
||||
return array_merge([
|
||||
'label' => t('file.sort'),
|
||||
'type' => 'select',
|
||||
'empty' => false,
|
||||
'options' => $options
|
||||
], $props);
|
||||
}
|
||||
return array_merge([
|
||||
'label' => I18n::translate('file.sort'),
|
||||
'type' => 'select',
|
||||
'empty' => false,
|
||||
'options' => $options
|
||||
], $props);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function hidden(): array
|
||||
{
|
||||
return ['type' => 'hidden'];
|
||||
}
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function hidden(): array
|
||||
{
|
||||
return ['type' => 'hidden'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Page position
|
||||
*
|
||||
* @param \Kirby\Cms\Page
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function pagePosition(Page $page, array $props = []): array
|
||||
{
|
||||
$index = 0;
|
||||
$options = [];
|
||||
$siblings = $page->parentModel()->children()->listed()->not($page);
|
||||
/**
|
||||
* Page position
|
||||
*
|
||||
* @param \Kirby\Cms\Page
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function pagePosition(Page $page, array $props = []): array
|
||||
{
|
||||
$index = 0;
|
||||
$options = [];
|
||||
$siblings = $page->parentModel()->children()->listed()->not($page);
|
||||
|
||||
foreach ($siblings as $sibling) {
|
||||
$index++;
|
||||
foreach ($siblings as $sibling) {
|
||||
$index++;
|
||||
|
||||
$options[] = [
|
||||
'value' => $index,
|
||||
'text' => $index
|
||||
];
|
||||
$options[] = [
|
||||
'value' => $index,
|
||||
'text' => $index
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'value' => $sibling->id(),
|
||||
'text' => $sibling->title()->value(),
|
||||
'disabled' => true
|
||||
];
|
||||
}
|
||||
$options[] = [
|
||||
'value' => $sibling->id(),
|
||||
'text' => $sibling->title()->value(),
|
||||
'disabled' => true
|
||||
];
|
||||
}
|
||||
|
||||
$index++;
|
||||
$index++;
|
||||
|
||||
$options[] = [
|
||||
'value' => $index,
|
||||
'text' => $index
|
||||
];
|
||||
$options[] = [
|
||||
'value' => $index,
|
||||
'text' => $index
|
||||
];
|
||||
|
||||
// if only one available option,
|
||||
// hide field when not in debug mode
|
||||
if (count($options) < 2) {
|
||||
return static::hidden();
|
||||
}
|
||||
// if only one available option,
|
||||
// hide field when not in debug mode
|
||||
if (count($options) < 2) {
|
||||
return static::hidden();
|
||||
}
|
||||
|
||||
return array_merge([
|
||||
'label' => t('page.changeStatus.position'),
|
||||
'type' => 'select',
|
||||
'empty' => false,
|
||||
'options' => $options,
|
||||
], $props);
|
||||
}
|
||||
return array_merge([
|
||||
'label' => I18n::translate('page.changeStatus.position'),
|
||||
'type' => 'select',
|
||||
'empty' => false,
|
||||
'options' => $options,
|
||||
], $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* A regular password field
|
||||
*
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function password(array $props = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'label' => t('password'),
|
||||
'type' => 'password'
|
||||
], $props);
|
||||
}
|
||||
/**
|
||||
* A regular password field
|
||||
*
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function password(array $props = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'label' => I18n::translate('password'),
|
||||
'type' => 'password'
|
||||
], $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* User role radio buttons
|
||||
*
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function role(array $props = []): array
|
||||
{
|
||||
$kirby = kirby();
|
||||
$user = $kirby->user();
|
||||
$isAdmin = $user && $user->isAdmin();
|
||||
$roles = [];
|
||||
/**
|
||||
* User role radio buttons
|
||||
*
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function role(array $props = []): array
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$user = $kirby->user();
|
||||
$isAdmin = $user && $user->isAdmin();
|
||||
$roles = [];
|
||||
|
||||
foreach ($kirby->roles() as $role) {
|
||||
// exclude the admin role, if the user
|
||||
// is not allowed to change role to admin
|
||||
if ($role->name() === 'admin' && $isAdmin === false) {
|
||||
continue;
|
||||
}
|
||||
foreach ($kirby->roles() as $role) {
|
||||
// exclude the admin role, if the user
|
||||
// is not allowed to change role to admin
|
||||
if ($role->name() === 'admin' && $isAdmin === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$roles[] = [
|
||||
'text' => $role->title(),
|
||||
'info' => $role->description() ?? t('role.description.placeholder'),
|
||||
'value' => $role->name()
|
||||
];
|
||||
}
|
||||
$roles[] = [
|
||||
'text' => $role->title(),
|
||||
'info' => $role->description() ?? I18n::translate('role.description.placeholder'),
|
||||
'value' => $role->name()
|
||||
];
|
||||
}
|
||||
|
||||
return array_merge([
|
||||
'label' => t('role'),
|
||||
'type' => count($roles) <= 1 ? 'hidden' : 'radio',
|
||||
'options' => $roles
|
||||
], $props);
|
||||
}
|
||||
return array_merge([
|
||||
'label' => I18n::translate('role'),
|
||||
'type' => count($roles) <= 1 ? 'hidden' : 'radio',
|
||||
'options' => $roles
|
||||
], $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function slug(array $props = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'label' => t('slug'),
|
||||
'type' => 'slug',
|
||||
], $props);
|
||||
}
|
||||
/**
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function slug(array $props = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'label' => I18n::translate('slug'),
|
||||
'type' => 'slug',
|
||||
], $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $blueprints
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function template(?array $blueprints = [], ?array $props = []): array
|
||||
{
|
||||
$options = [];
|
||||
/**
|
||||
* @param array $blueprints
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function template(?array $blueprints = [], ?array $props = []): array
|
||||
{
|
||||
$options = [];
|
||||
|
||||
foreach ($blueprints as $blueprint) {
|
||||
$options[] = [
|
||||
'text' => $blueprint['title'] ?? $blueprint['text'] ?? null,
|
||||
'value' => $blueprint['name'] ?? $blueprint['value'] ?? null,
|
||||
];
|
||||
}
|
||||
foreach ($blueprints as $blueprint) {
|
||||
$options[] = [
|
||||
'text' => $blueprint['title'] ?? $blueprint['text'] ?? null,
|
||||
'value' => $blueprint['name'] ?? $blueprint['value'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
return array_merge([
|
||||
'label' => t('template'),
|
||||
'type' => 'select',
|
||||
'empty' => false,
|
||||
'options' => $options,
|
||||
'icon' => 'template',
|
||||
'disabled' => count($options) <= 1
|
||||
], $props);
|
||||
}
|
||||
return array_merge([
|
||||
'label' => I18n::translate('template'),
|
||||
'type' => 'select',
|
||||
'empty' => false,
|
||||
'options' => $options,
|
||||
'icon' => 'template',
|
||||
'disabled' => count($options) <= 1
|
||||
], $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function title(array $props = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'label' => t('title'),
|
||||
'type' => 'text',
|
||||
'icon' => 'title',
|
||||
], $props);
|
||||
}
|
||||
/**
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function title(array $props = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'label' => I18n::translate('title'),
|
||||
'type' => 'text',
|
||||
'icon' => 'title',
|
||||
], $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Panel translation select box
|
||||
*
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function translation(array $props = []): array
|
||||
{
|
||||
$translations = [];
|
||||
foreach (kirby()->translations() as $translation) {
|
||||
$translations[] = [
|
||||
'text' => $translation->name(),
|
||||
'value' => $translation->code()
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Panel translation select box
|
||||
*
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function translation(array $props = []): array
|
||||
{
|
||||
$translations = [];
|
||||
foreach (App::instance()->translations() as $translation) {
|
||||
$translations[] = [
|
||||
'text' => $translation->name(),
|
||||
'value' => $translation->code()
|
||||
];
|
||||
}
|
||||
|
||||
return array_merge([
|
||||
'label' => t('language'),
|
||||
'type' => 'select',
|
||||
'icon' => 'globe',
|
||||
'options' => $translations,
|
||||
'empty' => false
|
||||
], $props);
|
||||
}
|
||||
return array_merge([
|
||||
'label' => I18n::translate('language'),
|
||||
'type' => 'select',
|
||||
'icon' => 'globe',
|
||||
'options' => $translations,
|
||||
'empty' => false
|
||||
], $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function username(array $props = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'icon' => 'user',
|
||||
'label' => t('name'),
|
||||
'type' => 'text',
|
||||
], $props);
|
||||
}
|
||||
/**
|
||||
* @param array $props
|
||||
* @return array
|
||||
*/
|
||||
public static function username(array $props = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'icon' => 'user',
|
||||
'label' => I18n::translate('name'),
|
||||
'type' => 'text',
|
||||
], $props);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Kirby\Panel;
|
||||
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
|
@ -16,457 +17,454 @@ use Throwable;
|
|||
*/
|
||||
class File extends Model
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Cms\File
|
||||
*/
|
||||
protected $model;
|
||||
/**
|
||||
* @var \Kirby\Cms\File
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Breadcrumb array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function breadcrumb(): array
|
||||
{
|
||||
$breadcrumb = [];
|
||||
$parent = $this->model->parent();
|
||||
/**
|
||||
* Breadcrumb array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function breadcrumb(): array
|
||||
{
|
||||
$breadcrumb = [];
|
||||
$parent = $this->model->parent();
|
||||
|
||||
switch ($parent::CLASS_ALIAS) {
|
||||
case 'user':
|
||||
// The breadcrumb is not necessary
|
||||
// on the account view
|
||||
if ($parent->isLoggedIn() === false) {
|
||||
$breadcrumb[] = [
|
||||
'label' => $parent->username(),
|
||||
'link' => $parent->panel()->url(true)
|
||||
];
|
||||
}
|
||||
break;
|
||||
case 'page':
|
||||
$breadcrumb = $this->model->parents()->flip()->values(fn ($parent) => [
|
||||
'label' => $parent->title()->toString(),
|
||||
'link' => $parent->panel()->url(true),
|
||||
]);
|
||||
}
|
||||
switch ($parent::CLASS_ALIAS) {
|
||||
case 'user':
|
||||
// The breadcrumb is not necessary
|
||||
// on the account view
|
||||
if ($parent->isLoggedIn() === false) {
|
||||
$breadcrumb[] = [
|
||||
'label' => $parent->username(),
|
||||
'link' => $parent->panel()->url(true)
|
||||
];
|
||||
}
|
||||
break;
|
||||
case 'page':
|
||||
$breadcrumb = $this->model->parents()->flip()->values(fn ($parent) => [
|
||||
'label' => $parent->title()->toString(),
|
||||
'link' => $parent->panel()->url(true),
|
||||
]);
|
||||
}
|
||||
|
||||
// add the file
|
||||
$breadcrumb[] = [
|
||||
'label' => $this->model->filename(),
|
||||
'link' => $this->url(true),
|
||||
];
|
||||
// add the file
|
||||
$breadcrumb[] = [
|
||||
'label' => $this->model->filename(),
|
||||
'link' => $this->url(true),
|
||||
];
|
||||
|
||||
return $breadcrumb;
|
||||
}
|
||||
return $breadcrumb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a kirbytag or markdown
|
||||
* tag for the file, which will be
|
||||
* used in the panel, when the file
|
||||
* gets dragged onto a textarea
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $type (`auto`|`kirbytext`|`markdown`)
|
||||
* @param bool $absolute
|
||||
* @return string
|
||||
*/
|
||||
public function dragText(string $type = null, bool $absolute = false): string
|
||||
{
|
||||
$type = $this->dragTextType($type);
|
||||
$url = $absolute ? $this->model->id() : $this->model->filename();
|
||||
/**
|
||||
* Provides a kirbytag or markdown
|
||||
* tag for the file, which will be
|
||||
* used in the panel, when the file
|
||||
* gets dragged onto a textarea
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $type (`auto`|`kirbytext`|`markdown`)
|
||||
* @param bool $absolute
|
||||
* @return string
|
||||
*/
|
||||
public function dragText(string $type = null, bool $absolute = false): string
|
||||
{
|
||||
$type = $this->dragTextType($type);
|
||||
$url = $absolute ? $this->model->id() : $this->model->filename();
|
||||
|
||||
if ($dragTextFromCallback = $this->dragTextFromCallback($type, $url)) {
|
||||
return $dragTextFromCallback;
|
||||
}
|
||||
if ($dragTextFromCallback = $this->dragTextFromCallback($type, $url)) {
|
||||
return $dragTextFromCallback;
|
||||
}
|
||||
|
||||
if ($type === 'markdown') {
|
||||
if ($this->model->type() === 'image') {
|
||||
return '';
|
||||
}
|
||||
if ($type === 'markdown') {
|
||||
if ($this->model->type() === 'image') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '[' . $this->model->filename() . '](' . $url . ')';
|
||||
}
|
||||
return '[' . $this->model->filename() . '](' . $url . ')';
|
||||
}
|
||||
|
||||
if ($this->model->type() === 'image') {
|
||||
return '(image: ' . $url . ')';
|
||||
}
|
||||
if ($this->model->type() === 'video') {
|
||||
return '(video: ' . $url . ')';
|
||||
}
|
||||
if ($this->model->type() === 'image') {
|
||||
return '(image: ' . $url . ')';
|
||||
}
|
||||
if ($this->model->type() === 'video') {
|
||||
return '(video: ' . $url . ')';
|
||||
}
|
||||
|
||||
return '(file: ' . $url . ')';
|
||||
}
|
||||
return '(file: ' . $url . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides options for the file dropdown
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function dropdown(array $options = []): array
|
||||
{
|
||||
$defaults = [
|
||||
'view' => get('view'),
|
||||
'update' => get('update'),
|
||||
'delete' => get('delete')
|
||||
];
|
||||
/**
|
||||
* Provides options for the file dropdown
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function dropdown(array $options = []): array
|
||||
{
|
||||
$file = $this->model;
|
||||
|
||||
$options = array_merge($defaults, $options);
|
||||
$file = $this->model;
|
||||
$permissions = $this->options(['preview']);
|
||||
$view = $options['view'] ?? 'view';
|
||||
$url = $this->url(true);
|
||||
$result = [];
|
||||
$defaults = $file->kirby()->request()->get(['view', 'update', 'delete']);
|
||||
$options = array_merge($defaults, $options);
|
||||
|
||||
if ($view === 'list') {
|
||||
$result[] = [
|
||||
'link' => $file->previewUrl(),
|
||||
'target' => '_blank',
|
||||
'icon' => 'open',
|
||||
'text' => t('open')
|
||||
];
|
||||
$result[] = '-';
|
||||
}
|
||||
$permissions = $this->options(['preview']);
|
||||
$view = $options['view'] ?? 'view';
|
||||
$url = $this->url(true);
|
||||
$result = [];
|
||||
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changeName',
|
||||
'icon' => 'title',
|
||||
'text' => t('rename'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeName', $options, $permissions)
|
||||
];
|
||||
if ($view === 'list') {
|
||||
$result[] = [
|
||||
'link' => $file->previewUrl(),
|
||||
'target' => '_blank',
|
||||
'icon' => 'open',
|
||||
'text' => I18n::translate('open')
|
||||
];
|
||||
$result[] = '-';
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'click' => 'replace',
|
||||
'icon' => 'upload',
|
||||
'text' => t('replace'),
|
||||
'disabled' => $this->isDisabledDropdownOption('replace', $options, $permissions)
|
||||
];
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changeName',
|
||||
'icon' => 'title',
|
||||
'text' => I18n::translate('rename'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeName', $options, $permissions)
|
||||
];
|
||||
|
||||
if ($view === 'list') {
|
||||
$result[] = '-';
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changeSort',
|
||||
'icon' => 'sort',
|
||||
'text' => t('file.sort'),
|
||||
'disabled' => $this->isDisabledDropdownOption('update', $options, $permissions)
|
||||
];
|
||||
}
|
||||
$result[] = [
|
||||
'click' => 'replace',
|
||||
'icon' => 'upload',
|
||||
'text' => I18n::translate('replace'),
|
||||
'disabled' => $this->isDisabledDropdownOption('replace', $options, $permissions)
|
||||
];
|
||||
|
||||
$result[] = '-';
|
||||
$result[] = [
|
||||
'dialog' => $url . '/delete',
|
||||
'icon' => 'trash',
|
||||
'text' => t('delete'),
|
||||
'disabled' => $this->isDisabledDropdownOption('delete', $options, $permissions)
|
||||
];
|
||||
if ($view === 'list') {
|
||||
$result[] = '-';
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changeSort',
|
||||
'icon' => 'sort',
|
||||
'text' => I18n::translate('file.sort'),
|
||||
'disabled' => $this->isDisabledDropdownOption('update', $options, $permissions)
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
$result[] = '-';
|
||||
$result[] = [
|
||||
'dialog' => $url . '/delete',
|
||||
'icon' => 'trash',
|
||||
'text' => I18n::translate('delete'),
|
||||
'disabled' => $this->isDisabledDropdownOption('delete', $options, $permissions)
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns the setup for a dropdown option
|
||||
* which is used in the changes dropdown
|
||||
* for example.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dropdownOption(): array
|
||||
{
|
||||
return [
|
||||
'icon' => 'image',
|
||||
'text' => $this->model->filename(),
|
||||
] + parent::dropdownOption();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Panel icon color
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function imageColor(): string
|
||||
{
|
||||
$types = [
|
||||
'image' => 'orange-400',
|
||||
'video' => 'yellow-400',
|
||||
'document' => 'red-400',
|
||||
'audio' => 'aqua-400',
|
||||
'code' => 'blue-400',
|
||||
'archive' => 'white'
|
||||
];
|
||||
/**
|
||||
* Returns the setup for a dropdown option
|
||||
* which is used in the changes dropdown
|
||||
* for example.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dropdownOption(): array
|
||||
{
|
||||
return [
|
||||
'icon' => 'image',
|
||||
'text' => $this->model->filename(),
|
||||
] + parent::dropdownOption();
|
||||
}
|
||||
|
||||
$extensions = [
|
||||
'indd' => 'purple-400',
|
||||
'xls' => 'green-400',
|
||||
'xlsx' => 'green-400',
|
||||
'csv' => 'green-400',
|
||||
'docx' => 'blue-400',
|
||||
'doc' => 'blue-400',
|
||||
'rtf' => 'blue-400'
|
||||
];
|
||||
/**
|
||||
* Returns the Panel icon color
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function imageColor(): string
|
||||
{
|
||||
$types = [
|
||||
'image' => 'orange-400',
|
||||
'video' => 'yellow-400',
|
||||
'document' => 'red-400',
|
||||
'audio' => 'aqua-400',
|
||||
'code' => 'blue-400',
|
||||
'archive' => 'gray-500'
|
||||
];
|
||||
|
||||
return $extensions[$this->model->extension()] ??
|
||||
$types[$this->model->type()] ??
|
||||
parent::imageDefaults()['icon'];
|
||||
}
|
||||
$extensions = [
|
||||
'indd' => 'purple-400',
|
||||
'xls' => 'green-400',
|
||||
'xlsx' => 'green-400',
|
||||
'csv' => 'green-400',
|
||||
'docx' => 'blue-400',
|
||||
'doc' => 'blue-400',
|
||||
'rtf' => 'blue-400'
|
||||
];
|
||||
|
||||
/**
|
||||
* Default settings for the file's Panel image
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function imageDefaults(): array
|
||||
{
|
||||
return array_merge(parent::imageDefaults(), [
|
||||
'color' => $this->imageColor(),
|
||||
'icon' => $this->imageIcon(),
|
||||
]);
|
||||
}
|
||||
return $extensions[$this->model->extension()] ??
|
||||
$types[$this->model->type()] ??
|
||||
parent::imageDefaults()['color'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Panel icon type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function imageIcon(): string
|
||||
{
|
||||
$types = [
|
||||
'image' => 'file-image',
|
||||
'video' => 'file-video',
|
||||
'document' => 'file-document',
|
||||
'audio' => 'file-audio',
|
||||
'code' => 'file-code',
|
||||
'archive' => 'file-zip'
|
||||
];
|
||||
/**
|
||||
* Default settings for the file's Panel image
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function imageDefaults(): array
|
||||
{
|
||||
return array_merge(parent::imageDefaults(), [
|
||||
'color' => $this->imageColor(),
|
||||
'icon' => $this->imageIcon(),
|
||||
]);
|
||||
}
|
||||
|
||||
$extensions = [
|
||||
'xls' => 'file-spreadsheet',
|
||||
'xlsx' => 'file-spreadsheet',
|
||||
'csv' => 'file-spreadsheet',
|
||||
'docx' => 'file-word',
|
||||
'doc' => 'file-word',
|
||||
'rtf' => 'file-word',
|
||||
'mdown' => 'file-text',
|
||||
'md' => 'file-text'
|
||||
];
|
||||
/**
|
||||
* Returns the Panel icon type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function imageIcon(): string
|
||||
{
|
||||
$types = [
|
||||
'image' => 'image',
|
||||
'video' => 'video',
|
||||
'document' => 'document',
|
||||
'audio' => 'audio',
|
||||
'code' => 'code',
|
||||
'archive' => 'archive'
|
||||
];
|
||||
|
||||
return $extensions[$this->model->extension()] ??
|
||||
$types[$this->model->type()] ??
|
||||
parent::imageDefaults()['color'];
|
||||
}
|
||||
$extensions = [
|
||||
'xls' => 'table',
|
||||
'xlsx' => 'table',
|
||||
'csv' => 'table',
|
||||
'docx' => 'pen',
|
||||
'doc' => 'pen',
|
||||
'rtf' => 'pen',
|
||||
'mdown' => 'markdown',
|
||||
'md' => 'markdown'
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Filesystem\Asset|null
|
||||
*/
|
||||
protected function imageSource(string $query = null)
|
||||
{
|
||||
if ($query === null && $this->model->isViewable()) {
|
||||
return $this->model;
|
||||
}
|
||||
return $extensions[$this->model->extension()] ??
|
||||
$types[$this->model->type()] ??
|
||||
'file';
|
||||
}
|
||||
|
||||
return parent::imageSource($query);
|
||||
}
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Filesystem\Asset|null
|
||||
*/
|
||||
protected function imageSource(string $query = null)
|
||||
{
|
||||
if ($query === null && $this->model->isViewable()) {
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all actions
|
||||
* that can be performed in the Panel
|
||||
*
|
||||
* @param array $unlock An array of options that will be force-unlocked
|
||||
* @return array
|
||||
*/
|
||||
public function options(array $unlock = []): array
|
||||
{
|
||||
$options = parent::options($unlock);
|
||||
return parent::imageSource($query);
|
||||
}
|
||||
|
||||
try {
|
||||
// check if the file type is allowed at all,
|
||||
// otherwise it cannot be replaced
|
||||
$this->model->match($this->model->blueprint()->accept());
|
||||
} catch (Throwable $e) {
|
||||
$options['replace'] = false;
|
||||
}
|
||||
/**
|
||||
* Returns an array of all actions
|
||||
* that can be performed in the Panel
|
||||
*
|
||||
* @param array $unlock An array of options that will be force-unlocked
|
||||
* @return array
|
||||
*/
|
||||
public function options(array $unlock = []): array
|
||||
{
|
||||
$options = parent::options($unlock);
|
||||
|
||||
return $options;
|
||||
}
|
||||
try {
|
||||
// check if the file type is allowed at all,
|
||||
// otherwise it cannot be replaced
|
||||
$this->model->match($this->model->blueprint()->accept());
|
||||
} catch (Throwable $e) {
|
||||
$options['replace'] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function path(): string
|
||||
{
|
||||
return 'files/' . $this->model->filename();
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the response data for file pickers
|
||||
* and file fields
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
*/
|
||||
public function pickerData(array $params = []): array
|
||||
{
|
||||
$id = $this->model->id();
|
||||
$name = $this->model->filename();
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function path(): string
|
||||
{
|
||||
return 'files/' . $this->model->filename();
|
||||
}
|
||||
|
||||
if (empty($params['model']) === false) {
|
||||
$parent = $this->model->parent();
|
||||
$uuid = $parent === $params['model'] ? $name : $id;
|
||||
$absolute = $parent !== $params['model'];
|
||||
}
|
||||
/**
|
||||
* Prepares the response data for file pickers
|
||||
* and file fields
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
*/
|
||||
public function pickerData(array $params = []): array
|
||||
{
|
||||
$id = $this->model->id();
|
||||
$name = $this->model->filename();
|
||||
|
||||
$params['text'] ??= '{{ file.filename }}';
|
||||
if (empty($params['model']) === false) {
|
||||
$parent = $this->model->parent();
|
||||
$uuid = $parent === $params['model'] ? $name : $id;
|
||||
$absolute = $parent !== $params['model'];
|
||||
}
|
||||
|
||||
return array_merge(parent::pickerData($params), [
|
||||
'filename' => $name,
|
||||
'dragText' => $this->dragText('auto', $absolute ?? false),
|
||||
'type' => $this->model->type(),
|
||||
'url' => $this->model->url(),
|
||||
'uuid' => $uuid ?? $id,
|
||||
]);
|
||||
}
|
||||
$params['text'] ??= '{{ file.filename }}';
|
||||
|
||||
/**
|
||||
* Returns the data array for the
|
||||
* view's component props
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function props(): array
|
||||
{
|
||||
$file = $this->model;
|
||||
$dimensions = $file->dimensions();
|
||||
$siblings = $file->templateSiblings()->sortBy(
|
||||
'sort',
|
||||
'asc',
|
||||
'filename',
|
||||
'asc'
|
||||
);
|
||||
return array_merge(parent::pickerData($params), [
|
||||
'filename' => $name,
|
||||
'dragText' => $this->dragText('auto', $absolute ?? false),
|
||||
'type' => $this->model->type(),
|
||||
'url' => $this->model->url(),
|
||||
'uuid' => $uuid ?? $id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data array for the
|
||||
* view's component props
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function props(): array
|
||||
{
|
||||
$file = $this->model;
|
||||
$dimensions = $file->dimensions();
|
||||
$siblings = $file->templateSiblings()->sortBy(
|
||||
'sort',
|
||||
'asc',
|
||||
'filename',
|
||||
'asc'
|
||||
);
|
||||
|
||||
|
||||
return array_merge(
|
||||
parent::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(),
|
||||
],
|
||||
'preview' => [
|
||||
'image' => $this->image([
|
||||
'back' => 'transparent',
|
||||
'ratio' => '1/1'
|
||||
], 'cards'),
|
||||
'url' => $url = $file->previewUrl(),
|
||||
'details' => [
|
||||
[
|
||||
'title' => t('template'),
|
||||
'text' => $file->template() ?? '—'
|
||||
],
|
||||
[
|
||||
'title' => t('mime'),
|
||||
'text' => $file->mime()
|
||||
],
|
||||
[
|
||||
'title' => t('url'),
|
||||
'text' => $id,
|
||||
'link' => $url
|
||||
],
|
||||
[
|
||||
'title' => t('size'),
|
||||
'text' => $file->niceSize()
|
||||
],
|
||||
[
|
||||
'title' => t('dimensions'),
|
||||
'text' => $file->type() === 'image' ? $file->dimensions() . ' ' . t('pixel') : '—'
|
||||
],
|
||||
[
|
||||
'title' => t('orientation'),
|
||||
'text' => $file->type() === 'image' ? t('orientation.' . $dimensions->orientation()) : '—'
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
return array_merge(
|
||||
parent::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(),
|
||||
],
|
||||
'preview' => [
|
||||
'image' => $this->image([
|
||||
'back' => 'transparent',
|
||||
'ratio' => '1/1'
|
||||
], 'cards'),
|
||||
'url' => $url = $file->previewUrl(),
|
||||
'details' => [
|
||||
[
|
||||
'title' => I18n::translate('template'),
|
||||
'text' => $file->template() ?? '—'
|
||||
],
|
||||
[
|
||||
'title' => I18n::translate('mime'),
|
||||
'text' => $file->mime()
|
||||
],
|
||||
[
|
||||
'title' => I18n::translate('url'),
|
||||
'text' => $id,
|
||||
'link' => $url
|
||||
],
|
||||
[
|
||||
'title' => I18n::translate('size'),
|
||||
'text' => $file->niceSize()
|
||||
],
|
||||
[
|
||||
'title' => I18n::translate('dimensions'),
|
||||
'text' => $file->type() === 'image' ? $file->dimensions() . ' ' . I18n::translate('pixel') : '—'
|
||||
],
|
||||
[
|
||||
'title' => I18n::translate('orientation'),
|
||||
'text' => $file->type() === 'image' ? I18n::translate('orientation.' . $dimensions->orientation()) : '—'
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns navigation array with
|
||||
* previous and next file
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function prevNext(): array
|
||||
{
|
||||
$file = $this->model;
|
||||
$siblings = $file->templateSiblings()->sortBy(
|
||||
'sort',
|
||||
'asc',
|
||||
'filename',
|
||||
'asc'
|
||||
);
|
||||
/**
|
||||
* Returns navigation array with
|
||||
* previous and next file
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function prevNext(): array
|
||||
{
|
||||
$file = $this->model;
|
||||
$siblings = $file->templateSiblings()->sortBy(
|
||||
'sort',
|
||||
'asc',
|
||||
'filename',
|
||||
'asc'
|
||||
);
|
||||
|
||||
return [
|
||||
'next' => function () use ($file, $siblings): ?array {
|
||||
$next = $siblings->nth($siblings->indexOf($file) + 1);
|
||||
return $this->toPrevNextLink($next, 'filename');
|
||||
},
|
||||
'prev' => function () use ($file, $siblings): ?array {
|
||||
$prev = $siblings->nth($siblings->indexOf($file) - 1);
|
||||
return $this->toPrevNextLink($prev, 'filename');
|
||||
}
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Returns the url to the editing view
|
||||
* in the panel
|
||||
*
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
*/
|
||||
public function url(bool $relative = false): string
|
||||
{
|
||||
$parent = $this->model->parent()->panel()->url($relative);
|
||||
return $parent . '/' . $this->path();
|
||||
}
|
||||
return [
|
||||
'next' => function () use ($file, $siblings): ?array {
|
||||
$next = $siblings->nth($siblings->indexOf($file) + 1);
|
||||
return $this->toPrevNextLink($next, 'filename');
|
||||
},
|
||||
'prev' => function () use ($file, $siblings): ?array {
|
||||
$prev = $siblings->nth($siblings->indexOf($file) - 1);
|
||||
return $this->toPrevNextLink($prev, 'filename');
|
||||
}
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Returns the url to the editing view
|
||||
* in the panel
|
||||
*
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
*/
|
||||
public function url(bool $relative = false): string
|
||||
{
|
||||
$parent = $this->model->parent()->panel()->url($relative);
|
||||
return $parent . '/' . $this->path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data array for
|
||||
* this model's Panel view
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function view(): array
|
||||
{
|
||||
$file = $this->model;
|
||||
/**
|
||||
* Returns the data array for
|
||||
* this model's Panel view
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function view(): array
|
||||
{
|
||||
$file = $this->model;
|
||||
|
||||
return [
|
||||
'breadcrumb' => fn (): array => $file->panel()->breadcrumb(),
|
||||
'component' => 'k-file-view',
|
||||
'props' => $this->props(),
|
||||
'search' => 'files',
|
||||
'title' => $file->filename(),
|
||||
];
|
||||
}
|
||||
return [
|
||||
'breadcrumb' => fn (): array => $file->panel()->breadcrumb(),
|
||||
'component' => 'k-file-view',
|
||||
'props' => $this->props(),
|
||||
'search' => 'files',
|
||||
'title' => $file->filename(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
namespace Kirby\Panel;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Http\Router;
|
||||
use Kirby\Http\Uri;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Throwable;
|
||||
|
@ -29,233 +31,234 @@ use Throwable;
|
|||
*/
|
||||
class Home
|
||||
{
|
||||
/**
|
||||
* Returns an alternative URL if access
|
||||
* to the first choice is blocked.
|
||||
*
|
||||
* It will go through the entire menu and
|
||||
* take the first area which is not disabled
|
||||
* or locked in other ways
|
||||
*
|
||||
* @param \Kirby\Cms\User $user
|
||||
* @return string
|
||||
*/
|
||||
public static function alternative(User $user): string
|
||||
{
|
||||
$permissions = $user->role()->permissions();
|
||||
/**
|
||||
* Returns an alternative URL if access
|
||||
* to the first choice is blocked.
|
||||
*
|
||||
* It will go through the entire menu and
|
||||
* take the first area which is not disabled
|
||||
* or locked in other ways
|
||||
*
|
||||
* @param \Kirby\Cms\User $user
|
||||
* @return string
|
||||
*/
|
||||
public static function alternative(User $user): string
|
||||
{
|
||||
$permissions = $user->role()->permissions();
|
||||
|
||||
// no access to the panel? The only good alternative is the main url
|
||||
if ($permissions->for('access', 'panel') === false) {
|
||||
return site()->url();
|
||||
}
|
||||
// no access to the panel? The only good alternative is the main url
|
||||
if ($permissions->for('access', 'panel') === false) {
|
||||
return App::instance()->site()->url();
|
||||
}
|
||||
|
||||
// needed to create a proper menu
|
||||
$areas = Panel::areas();
|
||||
$menu = View::menu($areas, $permissions->toArray());
|
||||
// needed to create a proper menu
|
||||
$areas = Panel::areas();
|
||||
$menu = View::menu($areas, $permissions->toArray());
|
||||
|
||||
// go through the menu and search for the first
|
||||
// available view we can go to
|
||||
foreach ($menu as $menuItem) {
|
||||
// skip separators
|
||||
if ($menuItem === '-') {
|
||||
continue;
|
||||
}
|
||||
// go through the menu and search for the first
|
||||
// available view we can go to
|
||||
foreach ($menu as $menuItem) {
|
||||
// skip separators
|
||||
if ($menuItem === '-') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip disabled items
|
||||
if (($menuItem['disabled'] ?? false) === true) {
|
||||
continue;
|
||||
}
|
||||
// skip disabled items
|
||||
if (($menuItem['disabled'] ?? false) === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip the logout button
|
||||
if ($menuItem['id'] === 'logout') {
|
||||
continue;
|
||||
}
|
||||
// skip the logout button
|
||||
if ($menuItem['id'] === 'logout') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
return Panel::url($menuItem['link']);
|
||||
}
|
||||
return Panel::url($menuItem['link']);
|
||||
}
|
||||
|
||||
throw new NotFoundException('There’s no available Panel page to redirect to');
|
||||
}
|
||||
throw new NotFoundException('There’s no available Panel page to redirect to');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has access to the given
|
||||
* panel path. This is quite tricky, because we
|
||||
* need to call a trimmed down router to check
|
||||
* for available routes and their firewall status.
|
||||
*
|
||||
* @param \Kirby\Cms\User
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasAccess(User $user, string $path): bool
|
||||
{
|
||||
$areas = Panel::areas();
|
||||
$routes = Panel::routes($areas);
|
||||
/**
|
||||
* Checks if the user has access to the given
|
||||
* panel path. This is quite tricky, because we
|
||||
* need to call a trimmed down router to check
|
||||
* for available routes and their firewall status.
|
||||
*
|
||||
* @param \Kirby\Cms\User
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasAccess(User $user, string $path): bool
|
||||
{
|
||||
$areas = Panel::areas();
|
||||
$routes = Panel::routes($areas);
|
||||
|
||||
// Remove fallback routes. Otherwise a route
|
||||
// would be found even if the view does
|
||||
// not exist at all.
|
||||
foreach ($routes as $index => $route) {
|
||||
if ($route['pattern'] === '(:all)') {
|
||||
unset($routes[$index]);
|
||||
}
|
||||
}
|
||||
// Remove fallback routes. Otherwise a route
|
||||
// would be found even if the view does
|
||||
// not exist at all.
|
||||
foreach ($routes as $index => $route) {
|
||||
if ($route['pattern'] === '(:all)') {
|
||||
unset($routes[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
// create a dummy router to check if we can access this route at all
|
||||
try {
|
||||
return router($path, 'GET', $routes, function ($route) use ($user) {
|
||||
$auth = $route->attributes()['auth'] ?? true;
|
||||
$areaId = $route->attributes()['area'] ?? null;
|
||||
$type = $route->attributes()['type'] ?? 'view';
|
||||
// create a dummy router to check if we can access this route at all
|
||||
try {
|
||||
return Router::execute($path, 'GET', $routes, function ($route) use ($user) {
|
||||
$auth = $route->attributes()['auth'] ?? true;
|
||||
$areaId = $route->attributes()['area'] ?? null;
|
||||
$type = $route->attributes()['type'] ?? 'view';
|
||||
|
||||
// only allow redirects to views
|
||||
if ($type !== 'view') {
|
||||
return false;
|
||||
}
|
||||
// only allow redirects to views
|
||||
if ($type !== 'view') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if auth is not required the redirect is allowed
|
||||
if ($auth === false) {
|
||||
return true;
|
||||
}
|
||||
// if auth is not required the redirect is allowed
|
||||
if ($auth === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check the firewall
|
||||
return Panel::hasAccess($user, $areaId);
|
||||
});
|
||||
} catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// check the firewall
|
||||
return Panel::hasAccess($user, $areaId);
|
||||
});
|
||||
} catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given Uri has the same domain
|
||||
* as the index URL of the Kirby installation.
|
||||
* This is used to block external URLs to third-party
|
||||
* domains as redirect options.
|
||||
*
|
||||
* @param \Kirby\Http\Uri $uri
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasValidDomain(Uri $uri): bool
|
||||
{
|
||||
return $uri->domain() === (new Uri(site()->url()))->domain();
|
||||
}
|
||||
/**
|
||||
* Checks if the given Uri has the same domain
|
||||
* as the index URL of the Kirby installation.
|
||||
* This is used to block external URLs to third-party
|
||||
* domains as redirect options.
|
||||
*
|
||||
* @param \Kirby\Http\Uri $uri
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasValidDomain(Uri $uri): bool
|
||||
{
|
||||
$rootUrl = App::instance()->site()->url();
|
||||
return $uri->domain() === (new Uri($rootUrl))->domain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given URL is a Panel Url.
|
||||
*
|
||||
* @param string $url
|
||||
* @return bool
|
||||
*/
|
||||
public static function isPanelUrl(string $url): bool
|
||||
{
|
||||
return Str::startsWith($url, kirby()->url('panel'));
|
||||
}
|
||||
/**
|
||||
* Checks if the given URL is a Panel Url.
|
||||
*
|
||||
* @param string $url
|
||||
* @return bool
|
||||
*/
|
||||
public static function isPanelUrl(string $url): bool
|
||||
{
|
||||
return Str::startsWith($url, App::instance()->url('panel'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path after /panel/ which can then
|
||||
* be used in the router or to find a matching view
|
||||
*
|
||||
* @param string $url
|
||||
* @return string|null
|
||||
*/
|
||||
public static function panelPath(string $url): ?string
|
||||
{
|
||||
$after = Str::after($url, kirby()->url('panel'));
|
||||
return trim($after, '/');
|
||||
}
|
||||
/**
|
||||
* Returns the path after /panel/ which can then
|
||||
* be used in the router or to find a matching view
|
||||
*
|
||||
* @param string $url
|
||||
* @return string|null
|
||||
*/
|
||||
public static function panelPath(string $url): ?string
|
||||
{
|
||||
$after = Str::after($url, App::instance()->url('panel'));
|
||||
return trim($after, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Url that has been stored in the session
|
||||
* before the last logout. We take this Url if possible
|
||||
* to redirect the user back to the last point where they
|
||||
* left before they got logged out.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function remembered(): ?string
|
||||
{
|
||||
// check for a stored path after login
|
||||
$remembered = kirby()->session()->pull('panel.path');
|
||||
/**
|
||||
* Returns the Url that has been stored in the session
|
||||
* before the last logout. We take this Url if possible
|
||||
* to redirect the user back to the last point where they
|
||||
* left before they got logged out.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function remembered(): ?string
|
||||
{
|
||||
// check for a stored path after login
|
||||
$remembered = App::instance()->session()->pull('panel.path');
|
||||
|
||||
// convert the result to an absolute URL if available
|
||||
return $remembered ? Panel::url($remembered) : null;
|
||||
}
|
||||
// convert the result to an absolute URL if available
|
||||
return $remembered ? Panel::url($remembered) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find the best possible Url to redirect
|
||||
* the user to after the login.
|
||||
*
|
||||
* When the user got logged out, we try to send them back
|
||||
* to the point where they left.
|
||||
*
|
||||
* If they have a custom redirect Url defined in their blueprint
|
||||
* via the `home` option, we send them there if no Url is stored
|
||||
* in the session.
|
||||
*
|
||||
* If none of the options above find any result, we try to send
|
||||
* them to the site view.
|
||||
*
|
||||
* Before the redirect happens, the final Url is sanitized, the query
|
||||
* and params are removed to avoid any attacks and the domain is compared
|
||||
* to avoid redirects to external Urls.
|
||||
*
|
||||
* Afterwards, we also check for permissions before the redirect happens
|
||||
* to avoid redirects to inaccessible Panel views. In such a case
|
||||
* the next best accessible view is picked from the menu.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function url(): string
|
||||
{
|
||||
$user = kirby()->user();
|
||||
/**
|
||||
* Tries to find the best possible Url to redirect
|
||||
* the user to after the login.
|
||||
*
|
||||
* When the user got logged out, we try to send them back
|
||||
* to the point where they left.
|
||||
*
|
||||
* If they have a custom redirect Url defined in their blueprint
|
||||
* via the `home` option, we send them there if no Url is stored
|
||||
* in the session.
|
||||
*
|
||||
* If none of the options above find any result, we try to send
|
||||
* them to the site view.
|
||||
*
|
||||
* Before the redirect happens, the final Url is sanitized, the query
|
||||
* and params are removed to avoid any attacks and the domain is compared
|
||||
* to avoid redirects to external Urls.
|
||||
*
|
||||
* Afterwards, we also check for permissions before the redirect happens
|
||||
* to avoid redirects to inaccessible Panel views. In such a case
|
||||
* the next best accessible view is picked from the menu.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function url(): string
|
||||
{
|
||||
$user = App::instance()->user();
|
||||
|
||||
// if there's no authenticated user, all internal
|
||||
// redirects will be blocked and the user is redirected
|
||||
// to the login instead
|
||||
if (!$user) {
|
||||
return Panel::url('login');
|
||||
}
|
||||
// if there's no authenticated user, all internal
|
||||
// redirects will be blocked and the user is redirected
|
||||
// to the login instead
|
||||
if (!$user) {
|
||||
return Panel::url('login');
|
||||
}
|
||||
|
||||
// get the last visited url from the session or the custom home
|
||||
$url = static::remembered() ?? $user->panel()->home();
|
||||
// get the last visited url from the session or the custom home
|
||||
$url = static::remembered() ?? $user->panel()->home();
|
||||
|
||||
// inspect the given URL
|
||||
$uri = new Uri($url);
|
||||
// inspect the given URL
|
||||
$uri = new Uri($url);
|
||||
|
||||
// compare domains to avoid external redirects
|
||||
if (static::hasValidDomain($uri) !== true) {
|
||||
throw new InvalidArgumentException('External URLs are not allowed for Panel redirects');
|
||||
}
|
||||
// compare domains to avoid external redirects
|
||||
if (static::hasValidDomain($uri) !== true) {
|
||||
throw new InvalidArgumentException('External URLs are not allowed for Panel redirects');
|
||||
}
|
||||
|
||||
// remove all params to avoid
|
||||
// possible attack vectors
|
||||
$uri->params = '';
|
||||
$uri->query = '';
|
||||
// remove all params to avoid
|
||||
// possible attack vectors
|
||||
$uri->params = '';
|
||||
$uri->query = '';
|
||||
|
||||
// get a clean version of the URL
|
||||
$url = $uri->toString();
|
||||
// get a clean version of the URL
|
||||
$url = $uri->toString();
|
||||
|
||||
// Don't further inspect URLs outside of the Panel
|
||||
if (static::isPanelUrl($url) === false) {
|
||||
return $url;
|
||||
}
|
||||
// Don't further inspect URLs outside of the Panel
|
||||
if (static::isPanelUrl($url) === false) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// get the plain panel path
|
||||
$path = static::panelPath($url);
|
||||
// get the plain panel path
|
||||
$path = static::panelPath($url);
|
||||
|
||||
// a redirect to login, logout or installation
|
||||
// views would lead to an infinite redirect loop
|
||||
if (in_array($path, ['', 'login', 'logout', 'installation'], true) === true) {
|
||||
$path = 'site';
|
||||
}
|
||||
// a redirect to login, logout or installation
|
||||
// views would lead to an infinite redirect loop
|
||||
if (in_array($path, ['', 'login', 'logout', 'installation'], true) === true) {
|
||||
$path = 'site';
|
||||
}
|
||||
|
||||
// Check if the user can access the URL
|
||||
if (static::hasAccess($user, $path) === true) {
|
||||
return Panel::url($path);
|
||||
}
|
||||
// Check if the user can access the URL
|
||||
if (static::hasAccess($user, $path) === true) {
|
||||
return Panel::url($path);
|
||||
}
|
||||
|
||||
// Try to find an alternative
|
||||
return static::alternative($user);
|
||||
}
|
||||
// Try to find an alternative
|
||||
return static::alternative($user);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,61 +17,61 @@ namespace Kirby\Panel;
|
|||
*/
|
||||
abstract class Json
|
||||
{
|
||||
protected static $key = '$response';
|
||||
protected static $key = '$response';
|
||||
|
||||
/**
|
||||
* Renders the error response with the provided message
|
||||
*
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @return array
|
||||
*/
|
||||
public static function error(string $message, int $code = 404)
|
||||
{
|
||||
return [
|
||||
'code' => $code,
|
||||
'error' => $message
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Renders the error response with the provided message
|
||||
*
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @return array
|
||||
*/
|
||||
public static function error(string $message, int $code = 404)
|
||||
{
|
||||
return [
|
||||
'code' => $code,
|
||||
'error' => $message
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the JSON response for the Panel
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param array $options
|
||||
* @return mixed
|
||||
*/
|
||||
public static function response($data, array $options = [])
|
||||
{
|
||||
// handle redirects
|
||||
if (is_a($data, 'Kirby\Panel\Redirect') === true) {
|
||||
$data = [
|
||||
'redirect' => $data->location(),
|
||||
'code' => $data->code()
|
||||
];
|
||||
/**
|
||||
* Prepares the JSON response for the Panel
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param array $options
|
||||
* @return mixed
|
||||
*/
|
||||
public static function response($data, array $options = [])
|
||||
{
|
||||
// handle redirects
|
||||
if (is_a($data, 'Kirby\Panel\Redirect') === true) {
|
||||
$data = [
|
||||
'redirect' => $data->location(),
|
||||
'code' => $data->code()
|
||||
];
|
||||
|
||||
// handle Kirby exceptions
|
||||
} elseif (is_a($data, 'Kirby\Exception\Exception') === true) {
|
||||
$data = static::error($data->getMessage(), $data->getHttpCode());
|
||||
// handle Kirby exceptions
|
||||
} elseif (is_a($data, 'Kirby\Exception\Exception') === true) {
|
||||
$data = static::error($data->getMessage(), $data->getHttpCode());
|
||||
|
||||
// handle exceptions
|
||||
} elseif (is_a($data, 'Throwable') === true) {
|
||||
$data = static::error($data->getMessage(), 500);
|
||||
// handle exceptions
|
||||
} elseif (is_a($data, 'Throwable') === true) {
|
||||
$data = static::error($data->getMessage(), 500);
|
||||
|
||||
// only expect arrays from here on
|
||||
} elseif (is_array($data) === false) {
|
||||
$data = static::error('Invalid response', 500);
|
||||
}
|
||||
// only expect arrays from here on
|
||||
} elseif (is_array($data) === false) {
|
||||
$data = static::error('Invalid response', 500);
|
||||
}
|
||||
|
||||
if (empty($data) === true) {
|
||||
$data = static::error('The response is empty', 404);
|
||||
}
|
||||
if (empty($data) === true) {
|
||||
$data = static::error('The response is empty', 404);
|
||||
}
|
||||
|
||||
// always inject the response code
|
||||
$data['code'] ??= 200;
|
||||
$data['path'] = $options['path'] ?? null;
|
||||
$data['referrer'] = Panel::referrer();
|
||||
// always inject the response code
|
||||
$data['code'] ??= 200;
|
||||
$data['path'] = $options['path'] ?? null;
|
||||
$data['referrer'] = Panel::referrer();
|
||||
|
||||
return Panel::json([static::$key => $data], $data['code']);
|
||||
}
|
||||
return Panel::json([static::$key => $data], $data['code']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,431 +18,433 @@ use Kirby\Toolkit\A;
|
|||
*/
|
||||
abstract class Model
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Cms\ModelWithContent
|
||||
*/
|
||||
protected $model;
|
||||
/**
|
||||
* @var \Kirby\Cms\ModelWithContent
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* @param \Kirby\Cms\ModelWithContent $model
|
||||
*/
|
||||
public function __construct($model)
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
/**
|
||||
* @param \Kirby\Cms\ModelWithContent $model
|
||||
*/
|
||||
public function __construct($model)
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content values for the model
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function content(): array
|
||||
{
|
||||
return Form::for($this->model)->values();
|
||||
}
|
||||
/**
|
||||
* Get the content values for the model
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function content(): array
|
||||
{
|
||||
return Form::for($this->model)->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the drag text from a custom callback
|
||||
* if the callback is defined in the config
|
||||
* @internal
|
||||
*
|
||||
* @param string $type markdown or kirbytext
|
||||
* @param mixed ...$args
|
||||
* @return string|null
|
||||
*/
|
||||
public function dragTextFromCallback(string $type, ...$args): ?string
|
||||
{
|
||||
$option = 'panel.' . $type . '.' . $this->model::CLASS_ALIAS . 'DragText';
|
||||
$callback = option($option);
|
||||
/**
|
||||
* Returns the drag text from a custom callback
|
||||
* if the callback is defined in the config
|
||||
* @internal
|
||||
*
|
||||
* @param string $type markdown or kirbytext
|
||||
* @param mixed ...$args
|
||||
* @return string|null
|
||||
*/
|
||||
public function dragTextFromCallback(string $type, ...$args): ?string
|
||||
{
|
||||
$option = 'panel.' . $type . '.' . $this->model::CLASS_ALIAS . 'DragText';
|
||||
$callback = $this->model->kirby()->option($option);
|
||||
|
||||
if (
|
||||
empty($callback) === false &&
|
||||
is_a($callback, 'Closure') === true &&
|
||||
($dragText = $callback($this->model, ...$args)) !== null
|
||||
) {
|
||||
return $dragText;
|
||||
}
|
||||
if (
|
||||
empty($callback) === false &&
|
||||
is_a($callback, 'Closure') === true &&
|
||||
($dragText = $callback($this->model, ...$args)) !== null
|
||||
) {
|
||||
return $dragText;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the correct drag text type
|
||||
* depending on the given type or the
|
||||
* configuration
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param string|null $type (`auto`|`kirbytext`|`markdown`)
|
||||
* @return string
|
||||
*/
|
||||
public function dragTextType(string $type = null): string
|
||||
{
|
||||
$type ??= 'auto';
|
||||
/**
|
||||
* Returns the correct drag text type
|
||||
* depending on the given type or the
|
||||
* configuration
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param string|null $type (`auto`|`kirbytext`|`markdown`)
|
||||
* @return string
|
||||
*/
|
||||
public function dragTextType(string $type = null): string
|
||||
{
|
||||
$type ??= 'auto';
|
||||
|
||||
if ($type === 'auto') {
|
||||
$type = option('panel.kirbytext', true) ? 'kirbytext' : 'markdown';
|
||||
}
|
||||
if ($type === 'auto') {
|
||||
$kirby = $this->model->kirby();
|
||||
$type = $kirby->option('panel.kirbytext', true) ? 'kirbytext' : 'markdown';
|
||||
}
|
||||
|
||||
return $type === 'markdown' ? 'markdown' : 'kirbytext';
|
||||
}
|
||||
return $type === 'markdown' ? 'markdown' : 'kirbytext';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the setup for a dropdown option
|
||||
* which is used in the changes dropdown
|
||||
* for example.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dropdownOption(): array
|
||||
{
|
||||
return [
|
||||
'icon' => 'page',
|
||||
'link' => $this->url(),
|
||||
'text' => $this->model->id(),
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Returns the setup for a dropdown option
|
||||
* which is used in the changes dropdown
|
||||
* for example.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dropdownOption(): array
|
||||
{
|
||||
return [
|
||||
'icon' => 'page',
|
||||
'link' => $this->url(),
|
||||
'text' => $this->model->id(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Panel image definition
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param string|array|false|null $settings
|
||||
* @return array|null
|
||||
*/
|
||||
public function image($settings = [], string $layout = 'list'): ?array
|
||||
{
|
||||
// completely switched off
|
||||
if ($settings === false) {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Returns the Panel image definition
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param string|array|false|null $settings
|
||||
* @return array|null
|
||||
*/
|
||||
public function image($settings = [], string $layout = 'list'): ?array
|
||||
{
|
||||
// completely switched off
|
||||
if ($settings === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// skip image thumbnail if option
|
||||
// is explicitly set to show the icon
|
||||
if ($settings === 'icon') {
|
||||
$settings = [
|
||||
'query' => false
|
||||
];
|
||||
} elseif (is_string($settings) === true) {
|
||||
// convert string settings to proper array
|
||||
$settings = [
|
||||
'query' => $settings
|
||||
];
|
||||
}
|
||||
// skip image thumbnail if option
|
||||
// is explicitly set to show the icon
|
||||
if ($settings === 'icon') {
|
||||
$settings = [
|
||||
'query' => false
|
||||
];
|
||||
} elseif (is_string($settings) === true) {
|
||||
// convert string settings to proper array
|
||||
$settings = [
|
||||
'query' => $settings
|
||||
];
|
||||
}
|
||||
|
||||
// merge with defaults and blueprint option
|
||||
$settings = array_merge(
|
||||
$this->imageDefaults(),
|
||||
$settings ?? [],
|
||||
$this->model->blueprint()->image() ?? [],
|
||||
);
|
||||
// merge with defaults and blueprint option
|
||||
$settings = array_merge(
|
||||
$this->imageDefaults(),
|
||||
$settings ?? [],
|
||||
$this->model->blueprint()->image() ?? [],
|
||||
);
|
||||
|
||||
if ($image = $this->imageSource($settings['query'] ?? null)) {
|
||||
// main url
|
||||
$settings['url'] = $image->url();
|
||||
if ($image = $this->imageSource($settings['query'] ?? null)) {
|
||||
// main url
|
||||
$settings['url'] = $image->url();
|
||||
|
||||
// only create srcsets for resizable files
|
||||
if ($image->isResizable() === true) {
|
||||
$settings['src'] = static::imagePlaceholder();
|
||||
// only create srcsets for resizable files
|
||||
if ($image->isResizable() === true) {
|
||||
$settings['src'] = static::imagePlaceholder();
|
||||
|
||||
switch ($layout) {
|
||||
case 'cards':
|
||||
$sizes = [352, 864, 1408];
|
||||
break;
|
||||
case 'cardlets':
|
||||
$sizes = [96, 192];
|
||||
break;
|
||||
case 'list':
|
||||
default:
|
||||
$sizes = [38, 76];
|
||||
break;
|
||||
}
|
||||
switch ($layout) {
|
||||
case 'cards':
|
||||
$sizes = [352, 864, 1408];
|
||||
break;
|
||||
case 'cardlets':
|
||||
$sizes = [96, 192];
|
||||
break;
|
||||
case 'list':
|
||||
default:
|
||||
$sizes = [38, 76];
|
||||
break;
|
||||
}
|
||||
|
||||
if (($settings['cover'] ?? false) === false || $layout === 'cards') {
|
||||
$settings['srcset'] = $image->srcset($sizes);
|
||||
} else {
|
||||
$settings['srcset'] = $image->srcset([
|
||||
'1x' => [
|
||||
'width' => $sizes[0],
|
||||
'height' => $sizes[0],
|
||||
'crop' => 'center'
|
||||
],
|
||||
'2x' => [
|
||||
'width' => $sizes[1],
|
||||
'height' => $sizes[1],
|
||||
'crop' => 'center'
|
||||
]
|
||||
]);
|
||||
}
|
||||
} elseif ($image->isViewable() === true) {
|
||||
$settings['src'] = $image->url();
|
||||
}
|
||||
}
|
||||
if (($settings['cover'] ?? false) === false || $layout === 'cards') {
|
||||
$settings['srcset'] = $image->srcset($sizes);
|
||||
} else {
|
||||
$settings['srcset'] = $image->srcset([
|
||||
'1x' => [
|
||||
'width' => $sizes[0],
|
||||
'height' => $sizes[0],
|
||||
'crop' => 'center'
|
||||
],
|
||||
'2x' => [
|
||||
'width' => $sizes[1],
|
||||
'height' => $sizes[1],
|
||||
'crop' => 'center'
|
||||
]
|
||||
]);
|
||||
}
|
||||
} elseif ($image->isViewable() === true) {
|
||||
$settings['src'] = $image->url();
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($settings['query']) === true) {
|
||||
unset($settings['query']);
|
||||
}
|
||||
if (isset($settings['query']) === true) {
|
||||
unset($settings['query']);
|
||||
}
|
||||
|
||||
// resolve remaining options defined as query
|
||||
return A::map($settings, function ($option) {
|
||||
if (is_string($option) === false) {
|
||||
return $option;
|
||||
}
|
||||
// resolve remaining options defined as query
|
||||
return A::map($settings, function ($option) {
|
||||
if (is_string($option) === false) {
|
||||
return $option;
|
||||
}
|
||||
|
||||
return $this->model->toString($option);
|
||||
});
|
||||
}
|
||||
return $this->model->toString($option);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Default settings for Panel image
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function imageDefaults(): array
|
||||
{
|
||||
return [
|
||||
'back' => 'pattern',
|
||||
'color' => 'gray-500',
|
||||
'cover' => false,
|
||||
'icon' => 'page',
|
||||
'ratio' => '3/2',
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Default settings for Panel image
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function imageDefaults(): array
|
||||
{
|
||||
return [
|
||||
'back' => 'pattern',
|
||||
'color' => 'gray-500',
|
||||
'cover' => false,
|
||||
'icon' => 'page',
|
||||
'ratio' => '3/2',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data URI placeholder string for Panel image
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function imagePlaceholder(): string
|
||||
{
|
||||
return 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw';
|
||||
}
|
||||
/**
|
||||
* Data URI placeholder string for Panel image
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function imagePlaceholder(): string
|
||||
{
|
||||
return 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Filesystem\Asset|null
|
||||
*/
|
||||
protected function imageSource(?string $query = null)
|
||||
{
|
||||
$image = $this->model->query($query ?? null);
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Filesystem\Asset|null
|
||||
*/
|
||||
protected function imageSource(?string $query = null)
|
||||
{
|
||||
$image = $this->model->query($query ?? null);
|
||||
|
||||
// validate the query result
|
||||
if (
|
||||
is_a($image, 'Kirby\Cms\File') === true ||
|
||||
is_a($image, 'Kirby\Filesystem\Asset') === true
|
||||
) {
|
||||
return $image;
|
||||
}
|
||||
// validate the query result
|
||||
if (
|
||||
is_a($image, 'Kirby\Cms\File') === true ||
|
||||
is_a($image, 'Kirby\Filesystem\Asset') === true
|
||||
) {
|
||||
return $image;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for disabled dropdown options according
|
||||
* to the given permissions
|
||||
*
|
||||
* @param string $action
|
||||
* @param array $options
|
||||
* @param array $permissions
|
||||
* @return bool
|
||||
*/
|
||||
public function isDisabledDropdownOption(string $action, array $options, array $permissions): bool
|
||||
{
|
||||
$option = $options[$action] ?? true;
|
||||
return $permissions[$action] === false || $option === false || $option === 'false';
|
||||
}
|
||||
/**
|
||||
* Checks for disabled dropdown options according
|
||||
* to the given permissions
|
||||
*
|
||||
* @param string $action
|
||||
* @param array $options
|
||||
* @param array $permissions
|
||||
* @return bool
|
||||
*/
|
||||
public function isDisabledDropdownOption(string $action, array $options, array $permissions): bool
|
||||
{
|
||||
$option = $options[$action] ?? true;
|
||||
return $permissions[$action] === false || $option === false || $option === 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns lock info for the Panel
|
||||
*
|
||||
* @return array|false array with lock info,
|
||||
* false if locking is not supported
|
||||
*/
|
||||
public function lock()
|
||||
{
|
||||
if ($lock = $this->model->lock()) {
|
||||
if ($lock->isUnlocked() === true) {
|
||||
return ['state' => 'unlock'];
|
||||
}
|
||||
/**
|
||||
* Returns lock info for the Panel
|
||||
*
|
||||
* @return array|false array with lock info,
|
||||
* false if locking is not supported
|
||||
*/
|
||||
public function lock()
|
||||
{
|
||||
if ($lock = $this->model->lock()) {
|
||||
if ($lock->isUnlocked() === true) {
|
||||
return ['state' => 'unlock'];
|
||||
}
|
||||
|
||||
if ($lock->isLocked() === true) {
|
||||
return [
|
||||
'state' => 'lock',
|
||||
'data' => $lock->get()
|
||||
];
|
||||
}
|
||||
if ($lock->isLocked() === true) {
|
||||
return [
|
||||
'state' => 'lock',
|
||||
'data' => $lock->get()
|
||||
];
|
||||
}
|
||||
|
||||
return ['state' => null];
|
||||
}
|
||||
return ['state' => null];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all actions
|
||||
* that can be performed in the Panel
|
||||
* This also checks for the lock status
|
||||
*
|
||||
* @param array $unlock An array of options that will be force-unlocked
|
||||
* @return array
|
||||
*/
|
||||
public function options(array $unlock = []): array
|
||||
{
|
||||
$options = $this->model->permissions()->toArray();
|
||||
/**
|
||||
* Returns an array of all actions
|
||||
* that can be performed in the Panel
|
||||
* This also checks for the lock status
|
||||
*
|
||||
* @param array $unlock An array of options that will be force-unlocked
|
||||
* @return array
|
||||
*/
|
||||
public function options(array $unlock = []): array
|
||||
{
|
||||
$options = $this->model->permissions()->toArray();
|
||||
|
||||
if ($this->model->isLocked()) {
|
||||
foreach ($options as $key => $value) {
|
||||
if (in_array($key, $unlock)) {
|
||||
continue;
|
||||
}
|
||||
if ($this->model->isLocked()) {
|
||||
foreach ($options as $key => $value) {
|
||||
if (in_array($key, $unlock)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$options[$key] = false;
|
||||
}
|
||||
}
|
||||
$options[$key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function path(): string;
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function path(): string;
|
||||
|
||||
/**
|
||||
* Prepares the response data for page pickers
|
||||
* and page fields
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
*/
|
||||
public function pickerData(array $params = []): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->model->id(),
|
||||
'image' => $this->image(
|
||||
$params['image'] ?? [],
|
||||
$params['layout'] ?? 'list'
|
||||
),
|
||||
'info' => $this->model->toSafeString($params['info'] ?? false),
|
||||
'link' => $this->url(true),
|
||||
'sortable' => true,
|
||||
'text' => $this->model->toSafeString($params['text'] ?? false),
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Prepares the response data for page pickers
|
||||
* and page fields
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
*/
|
||||
public function pickerData(array $params = []): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->model->id(),
|
||||
'image' => $this->image(
|
||||
$params['image'] ?? [],
|
||||
$params['layout'] ?? 'list'
|
||||
),
|
||||
'info' => $this->model->toSafeString($params['info'] ?? false),
|
||||
'link' => $this->url(true),
|
||||
'sortable' => true,
|
||||
'text' => $this->model->toSafeString($params['text'] ?? false),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data array for the
|
||||
* view's component props
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function props(): array
|
||||
{
|
||||
$blueprint = $this->model->blueprint();
|
||||
$tabs = $blueprint->tabs();
|
||||
$tab = $blueprint->tab(get('tab')) ?? $tabs[0] ?? null;
|
||||
/**
|
||||
* Returns the data array for the
|
||||
* view's component props
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function props(): array
|
||||
{
|
||||
$blueprint = $this->model->blueprint();
|
||||
$request = $this->model->kirby()->request();
|
||||
$tabs = $blueprint->tabs();
|
||||
$tab = $blueprint->tab($request->get('tab')) ?? $tabs[0] ?? null;
|
||||
|
||||
$props = [
|
||||
'lock' => $this->lock(),
|
||||
'permissions' => $this->model->permissions()->toArray(),
|
||||
'tabs' => $tabs,
|
||||
];
|
||||
$props = [
|
||||
'lock' => $this->lock(),
|
||||
'permissions' => $this->model->permissions()->toArray(),
|
||||
'tabs' => $tabs,
|
||||
];
|
||||
|
||||
// only send the tab if it exists
|
||||
// this will let the vue component define
|
||||
// a proper default value
|
||||
if ($tab) {
|
||||
$props['tab'] = $tab;
|
||||
}
|
||||
// only send the tab if it exists
|
||||
// this will let the vue component define
|
||||
// a proper default value
|
||||
if ($tab) {
|
||||
$props['tab'] = $tab;
|
||||
}
|
||||
|
||||
return $props;
|
||||
}
|
||||
return $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns link url and tooltip
|
||||
* for model (e.g. used for prev/next
|
||||
* navigation)
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param string $tooltip
|
||||
* @return array
|
||||
*/
|
||||
public function toLink(string $tooltip = 'title'): array
|
||||
{
|
||||
return [
|
||||
'link' => $this->url(true),
|
||||
'tooltip' => (string)$this->model->{$tooltip}()
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Returns link url and tooltip
|
||||
* for model (e.g. used for prev/next
|
||||
* navigation)
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param string $tooltip
|
||||
* @return array
|
||||
*/
|
||||
public function toLink(string $tooltip = 'title'): array
|
||||
{
|
||||
return [
|
||||
'link' => $this->url(true),
|
||||
'tooltip' => (string)$this->model->{$tooltip}()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns link url and tooltip
|
||||
* for optional sibling model and
|
||||
* preserves tab selection
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param \Kirby\Cms\ModelWithContent|null $model
|
||||
* @param string $tooltip
|
||||
* @return array
|
||||
*/
|
||||
protected function toPrevNextLink($model = null, string $tooltip = 'title'): ?array
|
||||
{
|
||||
if ($model === null) {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Returns link url and tooltip
|
||||
* for optional sibling model and
|
||||
* preserves tab selection
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param \Kirby\Cms\ModelWithContent|null $model
|
||||
* @param string $tooltip
|
||||
* @return array
|
||||
*/
|
||||
protected function toPrevNextLink($model = null, string $tooltip = 'title'): ?array
|
||||
{
|
||||
if ($model === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $model->panel()->toLink($tooltip);
|
||||
$data = $model->panel()->toLink($tooltip);
|
||||
|
||||
if ($tab = get('tab')) {
|
||||
$uri = new Uri($data['link'], [
|
||||
'query' => ['tab' => $tab]
|
||||
]);
|
||||
if ($tab = $model->kirby()->request()->get('tab')) {
|
||||
$uri = new Uri($data['link'], [
|
||||
'query' => ['tab' => $tab]
|
||||
]);
|
||||
|
||||
$data['link'] = $uri->toString();
|
||||
}
|
||||
$data['link'] = $uri->toString();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url to the editing view
|
||||
* in the Panel
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
*/
|
||||
public function url(bool $relative = false): string
|
||||
{
|
||||
if ($relative === true) {
|
||||
return '/' . $this->path();
|
||||
}
|
||||
/**
|
||||
* Returns the url to the editing view
|
||||
* in the Panel
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param bool $relative
|
||||
* @return string
|
||||
*/
|
||||
public function url(bool $relative = false): string
|
||||
{
|
||||
if ($relative === true) {
|
||||
return '/' . $this->path();
|
||||
}
|
||||
|
||||
return $this->model->kirby()->url('panel') . '/' . $this->path();
|
||||
}
|
||||
return $this->model->kirby()->url('panel') . '/' . $this->path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data array for
|
||||
* this model's Panel view
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function view(): array;
|
||||
/**
|
||||
* Returns the data array for
|
||||
* this model's Panel view
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function view(): array;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Kirby\Panel;
|
||||
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
/**
|
||||
* Provides information about the page model for the Panel
|
||||
* @since 3.6.0
|
||||
|
@ -14,363 +16,360 @@ namespace Kirby\Panel;
|
|||
*/
|
||||
class Page extends Model
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Cms\Page
|
||||
*/
|
||||
protected $model;
|
||||
/**
|
||||
* @var \Kirby\Cms\Page
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Breadcrumb array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function breadcrumb(): array
|
||||
{
|
||||
$parents = $this->model->parents()->flip()->merge($this->model);
|
||||
return $parents->values(fn ($parent) => [
|
||||
'label' => $parent->title()->toString(),
|
||||
'link' => $parent->panel()->url(true),
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* Breadcrumb array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function breadcrumb(): array
|
||||
{
|
||||
$parents = $this->model->parents()->flip()->merge($this->model);
|
||||
return $parents->values(fn ($parent) => [
|
||||
'label' => $parent->title()->toString(),
|
||||
'link' => $parent->panel()->url(true),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a kirbytag or markdown
|
||||
* tag for the page, which will be
|
||||
* used in the panel, when the page
|
||||
* gets dragged onto a textarea
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $type (`auto`|`kirbytext`|`markdown`)
|
||||
* @return string
|
||||
*/
|
||||
public function dragText(string $type = null): string
|
||||
{
|
||||
$type = $this->dragTextType($type);
|
||||
/**
|
||||
* Provides a kirbytag or markdown
|
||||
* tag for the page, which will be
|
||||
* used in the panel, when the page
|
||||
* gets dragged onto a textarea
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $type (`auto`|`kirbytext`|`markdown`)
|
||||
* @return string
|
||||
*/
|
||||
public function dragText(string $type = null): string
|
||||
{
|
||||
$type = $this->dragTextType($type);
|
||||
|
||||
if ($callback = $this->dragTextFromCallback($type)) {
|
||||
return $callback;
|
||||
}
|
||||
if ($callback = $this->dragTextFromCallback($type)) {
|
||||
return $callback;
|
||||
}
|
||||
|
||||
if ($type === 'markdown') {
|
||||
return '[' . $this->model->title() . '](' . $this->model->url() . ')';
|
||||
}
|
||||
if ($type === 'markdown') {
|
||||
return '[' . $this->model->title() . '](' . $this->model->url() . ')';
|
||||
}
|
||||
|
||||
return '(link: ' . $this->model->id() . ' text: ' . $this->model->title() . ')';
|
||||
}
|
||||
return '(link: ' . $this->model->id() . ' text: ' . $this->model->title() . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides options for the page dropdown
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function dropdown(array $options = []): array
|
||||
{
|
||||
$defaults = [
|
||||
'view' => get('view'),
|
||||
'sort' => get('sort'),
|
||||
'delete' => get('delete')
|
||||
];
|
||||
/**
|
||||
* Provides options for the page dropdown
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function dropdown(array $options = []): array
|
||||
{
|
||||
$page = $this->model;
|
||||
|
||||
$options = array_merge($defaults, $options);
|
||||
$page = $this->model;
|
||||
$permissions = $this->options(['preview']);
|
||||
$view = $options['view'] ?? 'view';
|
||||
$url = $this->url(true);
|
||||
$result = [];
|
||||
$defaults = $page->kirby()->request()->get(['view', 'sort', 'delete']);
|
||||
$options = array_merge($defaults, $options);
|
||||
|
||||
if ($view === 'list') {
|
||||
$result['preview'] = [
|
||||
'link' => $page->previewUrl(),
|
||||
'target' => '_blank',
|
||||
'icon' => 'open',
|
||||
'text' => t('open'),
|
||||
'disabled' => $this->isDisabledDropdownOption('preview', $options, $permissions)
|
||||
];
|
||||
$result[] = '-';
|
||||
}
|
||||
$permissions = $this->options(['preview']);
|
||||
$view = $options['view'] ?? 'view';
|
||||
$url = $this->url(true);
|
||||
$result = [];
|
||||
|
||||
$result['changeTitle'] = [
|
||||
'dialog' => [
|
||||
'url' => $url . '/changeTitle',
|
||||
'query' => [
|
||||
'select' => 'title'
|
||||
]
|
||||
],
|
||||
'icon' => 'title',
|
||||
'text' => t('rename'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeTitle', $options, $permissions)
|
||||
];
|
||||
if ($view === 'list') {
|
||||
$result['preview'] = [
|
||||
'link' => $page->previewUrl(),
|
||||
'target' => '_blank',
|
||||
'icon' => 'open',
|
||||
'text' => I18n::translate('open'),
|
||||
'disabled' => $this->isDisabledDropdownOption('preview', $options, $permissions)
|
||||
];
|
||||
$result[] = '-';
|
||||
}
|
||||
|
||||
$result['duplicate'] = [
|
||||
'dialog' => $url . '/duplicate',
|
||||
'icon' => 'copy',
|
||||
'text' => t('duplicate'),
|
||||
'disabled' => $this->isDisabledDropdownOption('duplicate', $options, $permissions)
|
||||
];
|
||||
$result['changeTitle'] = [
|
||||
'dialog' => [
|
||||
'url' => $url . '/changeTitle',
|
||||
'query' => [
|
||||
'select' => 'title'
|
||||
]
|
||||
],
|
||||
'icon' => 'title',
|
||||
'text' => I18n::translate('rename'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeTitle', $options, $permissions)
|
||||
];
|
||||
|
||||
$result[] = '-';
|
||||
$result['duplicate'] = [
|
||||
'dialog' => $url . '/duplicate',
|
||||
'icon' => 'copy',
|
||||
'text' => I18n::translate('duplicate'),
|
||||
'disabled' => $this->isDisabledDropdownOption('duplicate', $options, $permissions)
|
||||
];
|
||||
|
||||
$result['changeSlug'] = [
|
||||
'dialog' => [
|
||||
'url' => $url . '/changeTitle',
|
||||
'query' => [
|
||||
'select' => 'slug'
|
||||
]
|
||||
],
|
||||
'icon' => 'url',
|
||||
'text' => t('page.changeSlug'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeSlug', $options, $permissions)
|
||||
];
|
||||
$result[] = '-';
|
||||
|
||||
$result['changeStatus'] = [
|
||||
'dialog' => $url . '/changeStatus',
|
||||
'icon' => 'preview',
|
||||
'text' => t('page.changeStatus'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeStatus', $options, $permissions)
|
||||
];
|
||||
$result['changeSlug'] = [
|
||||
'dialog' => [
|
||||
'url' => $url . '/changeTitle',
|
||||
'query' => [
|
||||
'select' => 'slug'
|
||||
]
|
||||
],
|
||||
'icon' => 'url',
|
||||
'text' => I18n::translate('page.changeSlug'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeSlug', $options, $permissions)
|
||||
];
|
||||
|
||||
$siblings = $page->parentModel()->children()->listed()->not($page);
|
||||
$result['changeStatus'] = [
|
||||
'dialog' => $url . '/changeStatus',
|
||||
'icon' => 'preview',
|
||||
'text' => I18n::translate('page.changeStatus'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeStatus', $options, $permissions)
|
||||
];
|
||||
|
||||
$result['changeSort'] = [
|
||||
'dialog' => $url . '/changeSort',
|
||||
'icon' => 'sort',
|
||||
'text' => t('page.sort'),
|
||||
'disabled' => $siblings->count() === 0 || $this->isDisabledDropdownOption('sort', $options, $permissions)
|
||||
];
|
||||
$siblings = $page->parentModel()->children()->listed()->not($page);
|
||||
|
||||
$result['changeTemplate'] = [
|
||||
'dialog' => $url . '/changeTemplate',
|
||||
'icon' => 'template',
|
||||
'text' => t('page.changeTemplate'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeTemplate', $options, $permissions)
|
||||
];
|
||||
$result['changeSort'] = [
|
||||
'dialog' => $url . '/changeSort',
|
||||
'icon' => 'sort',
|
||||
'text' => I18n::translate('page.sort'),
|
||||
'disabled' => $siblings->count() === 0 || $this->isDisabledDropdownOption('sort', $options, $permissions)
|
||||
];
|
||||
|
||||
$result[] = '-';
|
||||
$result['delete'] = [
|
||||
'dialog' => $url . '/delete',
|
||||
'icon' => 'trash',
|
||||
'text' => t('delete'),
|
||||
'disabled' => $this->isDisabledDropdownOption('delete', $options, $permissions)
|
||||
];
|
||||
$result['changeTemplate'] = [
|
||||
'dialog' => $url . '/changeTemplate',
|
||||
'icon' => 'template',
|
||||
'text' => I18n::translate('page.changeTemplate'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeTemplate', $options, $permissions)
|
||||
];
|
||||
|
||||
return $result;
|
||||
}
|
||||
$result[] = '-';
|
||||
$result['delete'] = [
|
||||
'dialog' => $url . '/delete',
|
||||
'icon' => 'trash',
|
||||
'text' => I18n::translate('delete'),
|
||||
'disabled' => $this->isDisabledDropdownOption('delete', $options, $permissions)
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns the setup for a dropdown option
|
||||
* which is used in the changes dropdown
|
||||
* for example.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dropdownOption(): array
|
||||
{
|
||||
return [
|
||||
'text' => $this->model->title()->value(),
|
||||
] + parent::dropdownOption();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the escaped Id, which is
|
||||
* used in the panel to make routing work properly
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
return str_replace('/', '+', $this->model->id());
|
||||
}
|
||||
/**
|
||||
* Returns the setup for a dropdown option
|
||||
* which is used in the changes dropdown
|
||||
* for example.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dropdownOption(): array
|
||||
{
|
||||
return [
|
||||
'text' => $this->model->title()->value(),
|
||||
] + parent::dropdownOption();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default settings for the page's Panel image
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function imageDefaults(): array
|
||||
{
|
||||
$defaults = [];
|
||||
/**
|
||||
* Returns the escaped Id, which is
|
||||
* used in the panel to make routing work properly
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
return str_replace('/', '+', $this->model->id());
|
||||
}
|
||||
|
||||
if ($icon = $this->model->blueprint()->icon()) {
|
||||
$defaults['icon'] = $icon;
|
||||
}
|
||||
/**
|
||||
* Default settings for the page's Panel image
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function imageDefaults(): array
|
||||
{
|
||||
$defaults = [];
|
||||
|
||||
return array_merge(parent::imageDefaults(), $defaults);
|
||||
}
|
||||
if ($icon = $this->model->blueprint()->icon()) {
|
||||
$defaults['icon'] = $icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Filesystem\Asset|null
|
||||
*/
|
||||
protected function imageSource(string $query = null)
|
||||
{
|
||||
if ($query === null) {
|
||||
$query = 'page.image';
|
||||
}
|
||||
return array_merge(parent::imageDefaults(), $defaults);
|
||||
}
|
||||
|
||||
return parent::imageSource($query);
|
||||
}
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Filesystem\Asset|null
|
||||
*/
|
||||
protected function imageSource(string $query = null)
|
||||
{
|
||||
if ($query === null) {
|
||||
$query = 'page.image';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function path(): string
|
||||
{
|
||||
return 'pages/' . $this->id();
|
||||
}
|
||||
return parent::imageSource($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the response data for page pickers
|
||||
* and page fields
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
*/
|
||||
public function pickerData(array $params = []): array
|
||||
{
|
||||
$params['text'] ??= '{{ page.title }}';
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function path(): string
|
||||
{
|
||||
return 'pages/' . $this->id();
|
||||
}
|
||||
|
||||
return array_merge(parent::pickerData($params), [
|
||||
'dragText' => $this->dragText(),
|
||||
'hasChildren' => $this->model->hasChildren(),
|
||||
'url' => $this->model->url()
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* Prepares the response data for page pickers
|
||||
* and page fields
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
*/
|
||||
public function pickerData(array $params = []): array
|
||||
{
|
||||
$params['text'] ??= '{{ page.title }}';
|
||||
|
||||
/**
|
||||
* The best applicable position for
|
||||
* the position/status dialog
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function position(): int
|
||||
{
|
||||
return $this->model->num() ?? $this->model->parentModel()->children()->listed()->not($this->model)->count() + 1;
|
||||
}
|
||||
return array_merge(parent::pickerData($params), [
|
||||
'dragText' => $this->dragText(),
|
||||
'hasChildren' => $this->model->hasChildren(),
|
||||
'url' => $this->model->url()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns navigation array with
|
||||
* previous and next page
|
||||
* based on blueprint definition
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function prevNext(): array
|
||||
{
|
||||
$page = $this->model;
|
||||
/**
|
||||
* The best applicable position for
|
||||
* the position/status dialog
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function position(): int
|
||||
{
|
||||
return $this->model->num() ?? $this->model->parentModel()->children()->listed()->not($this->model)->count() + 1;
|
||||
}
|
||||
|
||||
// create siblings collection based on
|
||||
// blueprint navigation
|
||||
$siblings = function (string $direction) use ($page) {
|
||||
$navigation = $page->blueprint()->navigation();
|
||||
$sortBy = $navigation['sortBy'] ?? null;
|
||||
$status = $navigation['status'] ?? null;
|
||||
$template = $navigation['template'] ?? null;
|
||||
$direction = $direction === 'prev' ? 'prev' : 'next';
|
||||
/**
|
||||
* Returns navigation array with
|
||||
* previous and next page
|
||||
* based on blueprint definition
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function prevNext(): array
|
||||
{
|
||||
$page = $this->model;
|
||||
|
||||
// if status is defined in navigation,
|
||||
// all items in the collection are used
|
||||
// (drafts, listed and unlisted) otherwise
|
||||
// it depends on the status of the page
|
||||
$siblings = $status !== null ? $page->parentModel()->childrenAndDrafts() : $page->siblings();
|
||||
// create siblings collection based on
|
||||
// blueprint navigation
|
||||
$siblings = function (string $direction) use ($page) {
|
||||
$navigation = $page->blueprint()->navigation();
|
||||
$sortBy = $navigation['sortBy'] ?? null;
|
||||
$status = $navigation['status'] ?? null;
|
||||
$template = $navigation['template'] ?? null;
|
||||
$direction = $direction === 'prev' ? 'prev' : 'next';
|
||||
|
||||
// sort the collection if custom sortBy
|
||||
// defined in navigation otherwise
|
||||
// default sorting will apply
|
||||
if ($sortBy !== null) {
|
||||
$siblings = $siblings->sort(...$siblings::sortArgs($sortBy));
|
||||
}
|
||||
// if status is defined in navigation,
|
||||
// all items in the collection are used
|
||||
// (drafts, listed and unlisted) otherwise
|
||||
// it depends on the status of the page
|
||||
$siblings = $status !== null ? $page->parentModel()->childrenAndDrafts() : $page->siblings();
|
||||
|
||||
$siblings = $page->{$direction . 'All'}($siblings);
|
||||
// sort the collection if custom sortBy
|
||||
// defined in navigation otherwise
|
||||
// default sorting will apply
|
||||
if ($sortBy !== null) {
|
||||
$siblings = $siblings->sort(...$siblings::sortArgs($sortBy));
|
||||
}
|
||||
|
||||
if (empty($navigation) === false) {
|
||||
$statuses = (array)($status ?? $page->status());
|
||||
$templates = (array)($template ?? $page->intendedTemplate());
|
||||
$siblings = $page->{$direction . 'All'}($siblings);
|
||||
|
||||
// do not filter if template navigation is all
|
||||
if (in_array('all', $templates) === false) {
|
||||
$siblings = $siblings->filter('intendedTemplate', 'in', $templates);
|
||||
}
|
||||
if (empty($navigation) === false) {
|
||||
$statuses = (array)($status ?? $page->status());
|
||||
$templates = (array)($template ?? $page->intendedTemplate());
|
||||
|
||||
// do not filter if status navigation is all
|
||||
if (in_array('all', $statuses) === false) {
|
||||
$siblings = $siblings->filter('status', 'in', $statuses);
|
||||
}
|
||||
} else {
|
||||
$siblings = $siblings
|
||||
->filter('intendedTemplate', $page->intendedTemplate())
|
||||
->filter('status', $page->status());
|
||||
}
|
||||
// do not filter if template navigation is all
|
||||
if (in_array('all', $templates) === false) {
|
||||
$siblings = $siblings->filter('intendedTemplate', 'in', $templates);
|
||||
}
|
||||
|
||||
return $siblings->filter('isReadable', true);
|
||||
};
|
||||
// do not filter if status navigation is all
|
||||
if (in_array('all', $statuses) === false) {
|
||||
$siblings = $siblings->filter('status', 'in', $statuses);
|
||||
}
|
||||
} else {
|
||||
$siblings = $siblings
|
||||
->filter('intendedTemplate', $page->intendedTemplate())
|
||||
->filter('status', $page->status());
|
||||
}
|
||||
|
||||
return [
|
||||
'next' => fn () => $this->toPrevNextLink($siblings('next')->first()),
|
||||
'prev' => fn () => $this->toPrevNextLink($siblings('prev')->last())
|
||||
];
|
||||
}
|
||||
return $siblings->filter('isReadable', true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the data array for the
|
||||
* view's component props
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function props(): array
|
||||
{
|
||||
$page = $this->model;
|
||||
return [
|
||||
'next' => fn () => $this->toPrevNextLink($siblings('next')->first()),
|
||||
'prev' => fn () => $this->toPrevNextLink($siblings('prev')->last())
|
||||
];
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
parent::props(),
|
||||
$this->prevNext(),
|
||||
[
|
||||
'blueprint' => $this->model->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(),
|
||||
],
|
||||
'status' => function () use ($page) {
|
||||
if ($status = $page->status()) {
|
||||
return $page->blueprint()->status()[$status] ?? null;
|
||||
}
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Returns the data array for the
|
||||
* view's component props
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function props(): array
|
||||
{
|
||||
$page = $this->model;
|
||||
|
||||
/**
|
||||
* Returns the data array for
|
||||
* this model's Panel view
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function view(): array
|
||||
{
|
||||
$page = $this->model;
|
||||
return array_merge(
|
||||
parent::props(),
|
||||
$this->prevNext(),
|
||||
[
|
||||
'blueprint' => $this->model->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(),
|
||||
],
|
||||
'status' => function () use ($page) {
|
||||
if ($status = $page->status()) {
|
||||
return $page->blueprint()->status()[$status] ?? null;
|
||||
}
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'breadcrumb' => $page->panel()->breadcrumb(),
|
||||
'component' => 'k-page-view',
|
||||
'props' => $this->props(),
|
||||
'title' => $page->title()->toString(),
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Returns the data array for
|
||||
* this model's Panel view
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function view(): array
|
||||
{
|
||||
$page = $this->model;
|
||||
|
||||
return [
|
||||
'breadcrumb' => $page->panel()->breadcrumb(),
|
||||
'component' => 'k-page-view',
|
||||
'props' => $this->props(),
|
||||
'title' => $page->title()->toString(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,9 @@
|
|||
namespace Kirby\Panel;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Data\Json;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
|
@ -19,91 +21,131 @@ use Kirby\Toolkit\Str;
|
|||
*/
|
||||
class Plugins
|
||||
{
|
||||
/**
|
||||
* Cache of all collected plugin files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $files;
|
||||
/**
|
||||
* Cache of all collected plugin files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $files;
|
||||
|
||||
/**
|
||||
* Collects and returns the plugin files for all plugins
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function files(): array
|
||||
{
|
||||
if ($this->files !== null) {
|
||||
return $this->files;
|
||||
}
|
||||
/**
|
||||
* Collects and returns the plugin files for all plugins
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function files(): array
|
||||
{
|
||||
if ($this->files !== null) {
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
$this->files = [];
|
||||
$this->files = [];
|
||||
|
||||
foreach (App::instance()->plugins() as $plugin) {
|
||||
$this->files[] = $plugin->root() . '/index.css';
|
||||
$this->files[] = $plugin->root() . '/index.js';
|
||||
}
|
||||
foreach (App::instance()->plugins() as $plugin) {
|
||||
$this->files[] = $plugin->root() . '/index.css';
|
||||
$this->files[] = $plugin->root() . '/index.js';
|
||||
// During plugin development, kirbyup adds an index.dev.mjs as entry point, which
|
||||
// Kirby will load instead of the regular index.js. Since kirbyup is based on Vite,
|
||||
// it can't use the standard index.js as entry for its development server:
|
||||
// Vite requires an entry of type module so it can use JavaScript imports,
|
||||
// but Kirbyup needs index.js to load as a regular script, synchronously.
|
||||
$this->files[] = $plugin->root() . '/index.dev.mjs';
|
||||
}
|
||||
|
||||
return $this->files;
|
||||
}
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification
|
||||
* of the collected plugin files
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function modified(): int
|
||||
{
|
||||
$files = $this->files();
|
||||
$modified = [0];
|
||||
/**
|
||||
* Returns the last modification
|
||||
* of the collected plugin files
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function modified(): int
|
||||
{
|
||||
$files = $this->files();
|
||||
$modified = [0];
|
||||
|
||||
foreach ($files as $file) {
|
||||
$modified[] = F::modified($file);
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
$modified[] = F::modified($file);
|
||||
}
|
||||
|
||||
return max($modified);
|
||||
}
|
||||
return max($modified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the files from all plugins and concatenate them
|
||||
*
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
public function read(string $type): string
|
||||
{
|
||||
$dist = [];
|
||||
/**
|
||||
* Read the files from all plugins and concatenate them
|
||||
*
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
public function read(string $type): string
|
||||
{
|
||||
$dist = [];
|
||||
|
||||
foreach ($this->files() as $file) {
|
||||
if (F::extension($file) === $type) {
|
||||
if ($content = F::read($file)) {
|
||||
if ($type === 'js') {
|
||||
$content = trim($content);
|
||||
foreach ($this->files() as $file) {
|
||||
// filter out files with a different type
|
||||
if (F::extension($file) !== $type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// make sure that each plugin is ended correctly
|
||||
if (Str::endsWith($content, ';') === false) {
|
||||
$content .= ';';
|
||||
}
|
||||
}
|
||||
// filter out empty files and files that don't exist
|
||||
$content = F::read($file);
|
||||
if (!$content) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dist[] = $content;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($type === 'mjs') {
|
||||
// index.dev.mjs files are turned into data URIs so they
|
||||
// can be imported without having to copy them to /media
|
||||
// (avoids having to clean the files from /media again)
|
||||
$content = F::uri($file);
|
||||
}
|
||||
|
||||
return implode(PHP_EOL . PHP_EOL, $dist);
|
||||
}
|
||||
if ($type === 'js') {
|
||||
// filter out all index.js files that shouldn't be loaded
|
||||
// because an index.dev.mjs exists
|
||||
if (F::exists(preg_replace('/\.js$/', '.dev.mjs', $file)) === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute url to the cache file
|
||||
* This is used by the panel to link the plugins
|
||||
*
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
public function url(string $type): string
|
||||
{
|
||||
return App::instance()->url('media') . '/plugins/index.' . $type . '?' . $this->modified();
|
||||
}
|
||||
$content = trim($content);
|
||||
|
||||
// make sure that each plugin is ended correctly
|
||||
if (Str::endsWith($content, ';') === false) {
|
||||
$content .= ';';
|
||||
}
|
||||
}
|
||||
|
||||
$dist[] = $content;
|
||||
}
|
||||
|
||||
if ($type === 'mjs') {
|
||||
// if no index.dev.mjs modules exist, we MUST return an empty string instead
|
||||
// of loading an empty array; this is because the module loader code uses
|
||||
// top level await, which is not compatible with Kirby's minimum browser
|
||||
// version requirements and therefore must not appear in a default setup
|
||||
if (empty($dist)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$modules = Json::encode($dist);
|
||||
$modulePromise = "Promise.all($modules.map(url => import(url)))";
|
||||
return "try { await $modulePromise } catch (e) { console.error(e) }" . PHP_EOL;
|
||||
}
|
||||
|
||||
return implode(PHP_EOL . PHP_EOL, $dist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute url to the cache file
|
||||
* This is used by the panel to link the plugins
|
||||
*
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
public function url(string $type): string
|
||||
{
|
||||
return App::instance()->url('media') . '/plugins/index.' . $type . '?' . $this->modified();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,29 +18,29 @@ use Exception;
|
|||
*/
|
||||
class Redirect extends Exception
|
||||
{
|
||||
/**
|
||||
* Returns the HTTP code for the redirect
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function code(): int
|
||||
{
|
||||
$codes = [301, 302, 303, 307, 308];
|
||||
/**
|
||||
* Returns the HTTP code for the redirect
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function code(): int
|
||||
{
|
||||
$codes = [301, 302, 303, 307, 308];
|
||||
|
||||
if (in_array($this->getCode(), $codes) === true) {
|
||||
return $this->getCode();
|
||||
}
|
||||
if (in_array($this->getCode(), $codes) === true) {
|
||||
return $this->getCode();
|
||||
}
|
||||
|
||||
return 302;
|
||||
}
|
||||
return 302;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL for the redirect
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function location(): string
|
||||
{
|
||||
return $this->getMessage();
|
||||
}
|
||||
/**
|
||||
* Returns the URL for the redirect
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function location(): string
|
||||
{
|
||||
return $this->getMessage();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,21 +16,21 @@ namespace Kirby\Panel;
|
|||
*/
|
||||
class Search extends Json
|
||||
{
|
||||
protected static $key = '$search';
|
||||
protected static $key = '$search';
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
* @param array $options
|
||||
* @return \Kirby\Http\Response
|
||||
*/
|
||||
public static function response($data, array $options = [])
|
||||
{
|
||||
if (is_array($data) === true) {
|
||||
$data = [
|
||||
'results' => $data
|
||||
];
|
||||
}
|
||||
/**
|
||||
* @param mixed $data
|
||||
* @param array $options
|
||||
* @return \Kirby\Http\Response
|
||||
*/
|
||||
public static function response($data, array $options = [])
|
||||
{
|
||||
if (is_array($data) === true) {
|
||||
$data = [
|
||||
'results' => $data
|
||||
];
|
||||
}
|
||||
|
||||
return parent::response($data, $options);
|
||||
}
|
||||
return parent::response($data, $options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,86 +14,86 @@ namespace Kirby\Panel;
|
|||
*/
|
||||
class Site extends Model
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Cms\Site
|
||||
*/
|
||||
protected $model;
|
||||
/**
|
||||
* @var \Kirby\Cms\Site
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Returns the setup for a dropdown option
|
||||
* which is used in the changes dropdown
|
||||
* for example.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dropdownOption(): array
|
||||
{
|
||||
return [
|
||||
'icon' => 'home',
|
||||
'text' => $this->model->title()->value(),
|
||||
] + parent::dropdownOption();
|
||||
}
|
||||
/**
|
||||
* Returns the setup for a dropdown option
|
||||
* which is used in the changes dropdown
|
||||
* for example.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dropdownOption(): array
|
||||
{
|
||||
return [
|
||||
'icon' => 'home',
|
||||
'text' => $this->model->title()->value(),
|
||||
] + parent::dropdownOption();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Filesystem\Asset|null
|
||||
*/
|
||||
protected function imageSource(string $query = null)
|
||||
{
|
||||
if ($query === null) {
|
||||
$query = 'site.image';
|
||||
}
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @internal
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Filesystem\Asset|null
|
||||
*/
|
||||
protected function imageSource(string $query = null)
|
||||
{
|
||||
if ($query === null) {
|
||||
$query = 'site.image';
|
||||
}
|
||||
|
||||
return parent::imageSource($query);
|
||||
}
|
||||
return parent::imageSource($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function path(): string
|
||||
{
|
||||
return 'site';
|
||||
}
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function path(): string
|
||||
{
|
||||
return 'site';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data array for the
|
||||
* view's component props
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function props(): array
|
||||
{
|
||||
return array_merge(parent::props(), [
|
||||
'blueprint' => 'site',
|
||||
'model' => [
|
||||
'content' => $this->content(),
|
||||
'link' => $this->url(true),
|
||||
'previewUrl' => $this->model->previewUrl(),
|
||||
'title' => $this->model->title()->toString(),
|
||||
]
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* Returns the data array for the
|
||||
* view's component props
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function props(): array
|
||||
{
|
||||
return array_merge(parent::props(), [
|
||||
'blueprint' => 'site',
|
||||
'model' => [
|
||||
'content' => $this->content(),
|
||||
'link' => $this->url(true),
|
||||
'previewUrl' => $this->model->previewUrl(),
|
||||
'title' => $this->model->title()->toString(),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data array for
|
||||
* this model's Panel view
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function view(): array
|
||||
{
|
||||
return [
|
||||
'component' => 'k-site-view',
|
||||
'props' => $this->props()
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Returns the data array for
|
||||
* this model's Panel view
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function view(): array
|
||||
{
|
||||
return [
|
||||
'component' => 'k-site-view',
|
||||
'props' => $this->props()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
namespace Kirby\Panel;
|
||||
|
||||
use Kirby\Cms\Url;
|
||||
use Kirby\Toolkit\I18n;
|
||||
|
||||
/**
|
||||
* Provides information about the user model for the Panel
|
||||
* @since 3.6.0
|
||||
|
@ -14,258 +17,258 @@ namespace Kirby\Panel;
|
|||
*/
|
||||
class User extends Model
|
||||
{
|
||||
/**
|
||||
* @var \Kirby\Cms\User
|
||||
*/
|
||||
protected $model;
|
||||
/**
|
||||
* @var \Kirby\Cms\User
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Breadcrumb array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function breadcrumb(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'label' => $this->model->username(),
|
||||
'link' => $this->url(true),
|
||||
]
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Breadcrumb array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function breadcrumb(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'label' => $this->model->username(),
|
||||
'link' => $this->url(true),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides options for the user dropdown
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function dropdown(array $options = []): array
|
||||
{
|
||||
$account = $this->model->isLoggedIn();
|
||||
$i18nPrefix = $account ? 'account' : 'user';
|
||||
$permissions = $this->options(['preview']);
|
||||
$url = $this->url(true);
|
||||
$result = [];
|
||||
/**
|
||||
* Provides options for the user dropdown
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function dropdown(array $options = []): array
|
||||
{
|
||||
$account = $this->model->isLoggedIn();
|
||||
$i18nPrefix = $account ? 'account' : 'user';
|
||||
$permissions = $this->options(['preview']);
|
||||
$url = $this->url(true);
|
||||
$result = [];
|
||||
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changeName',
|
||||
'icon' => 'title',
|
||||
'text' => t($i18nPrefix . '.changeName'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeName', $options, $permissions)
|
||||
];
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changeName',
|
||||
'icon' => 'title',
|
||||
'text' => I18n::translate($i18nPrefix . '.changeName'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeName', $options, $permissions)
|
||||
];
|
||||
|
||||
$result[] = '-';
|
||||
$result[] = '-';
|
||||
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changeEmail',
|
||||
'icon' => 'email',
|
||||
'text' => t('user.changeEmail'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeEmail', $options, $permissions)
|
||||
];
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changeEmail',
|
||||
'icon' => 'email',
|
||||
'text' => I18n::translate('user.changeEmail'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeEmail', $options, $permissions)
|
||||
];
|
||||
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changeRole',
|
||||
'icon' => 'bolt',
|
||||
'text' => t('user.changeRole'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeRole', $options, $permissions)
|
||||
];
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changeRole',
|
||||
'icon' => 'bolt',
|
||||
'text' => I18n::translate('user.changeRole'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeRole', $options, $permissions)
|
||||
];
|
||||
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changePassword',
|
||||
'icon' => 'key',
|
||||
'text' => t('user.changePassword'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changePassword', $options, $permissions)
|
||||
];
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changePassword',
|
||||
'icon' => 'key',
|
||||
'text' => I18n::translate('user.changePassword'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changePassword', $options, $permissions)
|
||||
];
|
||||
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changeLanguage',
|
||||
'icon' => 'globe',
|
||||
'text' => t('user.changeLanguage'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeLanguage', $options, $permissions)
|
||||
];
|
||||
$result[] = [
|
||||
'dialog' => $url . '/changeLanguage',
|
||||
'icon' => 'globe',
|
||||
'text' => I18n::translate('user.changeLanguage'),
|
||||
'disabled' => $this->isDisabledDropdownOption('changeLanguage', $options, $permissions)
|
||||
];
|
||||
|
||||
$result[] = '-';
|
||||
$result[] = '-';
|
||||
|
||||
$result[] = [
|
||||
'dialog' => $url . '/delete',
|
||||
'icon' => 'trash',
|
||||
'text' => t($i18nPrefix . '.delete'),
|
||||
'disabled' => $this->isDisabledDropdownOption('delete', $options, $permissions)
|
||||
];
|
||||
$result[] = [
|
||||
'dialog' => $url . '/delete',
|
||||
'icon' => 'trash',
|
||||
'text' => I18n::translate($i18nPrefix . '.delete'),
|
||||
'disabled' => $this->isDisabledDropdownOption('delete', $options, $permissions)
|
||||
];
|
||||
|
||||
return $result;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the setup for a dropdown option
|
||||
* which is used in the changes dropdown
|
||||
* for example.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dropdownOption(): array
|
||||
{
|
||||
return [
|
||||
'icon' => 'user',
|
||||
'text' => $this->model->username(),
|
||||
] + parent::dropdownOption();
|
||||
}
|
||||
/**
|
||||
* Returns the setup for a dropdown option
|
||||
* which is used in the changes dropdown
|
||||
* for example.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dropdownOption(): array
|
||||
{
|
||||
return [
|
||||
'icon' => 'user',
|
||||
'text' => $this->model->username(),
|
||||
] + parent::dropdownOption();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function home(): ?string
|
||||
{
|
||||
if ($home = ($this->model->blueprint()->home() ?? null)) {
|
||||
$url = $this->model->toString($home);
|
||||
return url($url);
|
||||
}
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function home(): ?string
|
||||
{
|
||||
if ($home = ($this->model->blueprint()->home() ?? null)) {
|
||||
$url = $this->model->toString($home);
|
||||
return Url::to($url);
|
||||
}
|
||||
|
||||
return Panel::url('site');
|
||||
}
|
||||
return Panel::url('site');
|
||||
}
|
||||
|
||||
/**
|
||||
* Default settings for the user's Panel image
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function imageDefaults(): array
|
||||
{
|
||||
return array_merge(parent::imageDefaults(), [
|
||||
'back' => 'black',
|
||||
'icon' => 'user',
|
||||
'ratio' => '1/1',
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* Default settings for the user's Panel image
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function imageDefaults(): array
|
||||
{
|
||||
return array_merge(parent::imageDefaults(), [
|
||||
'back' => 'black',
|
||||
'icon' => 'user',
|
||||
'ratio' => '1/1',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Filesystem\Asset|null
|
||||
*/
|
||||
protected function imageSource(string $query = null)
|
||||
{
|
||||
if ($query === null) {
|
||||
return $this->model->avatar();
|
||||
}
|
||||
/**
|
||||
* Returns the image file object based on provided query
|
||||
*
|
||||
* @param string|null $query
|
||||
* @return \Kirby\Cms\File|\Kirby\Filesystem\Asset|null
|
||||
*/
|
||||
protected function imageSource(string $query = null)
|
||||
{
|
||||
if ($query === null) {
|
||||
return $this->model->avatar();
|
||||
}
|
||||
|
||||
return parent::imageSource($query);
|
||||
}
|
||||
return parent::imageSource($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function path(): string
|
||||
{
|
||||
// path to your own account
|
||||
if ($this->model->isLoggedIn() === true) {
|
||||
return 'account';
|
||||
}
|
||||
/**
|
||||
* Returns the full path without leading slash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function path(): string
|
||||
{
|
||||
// path to your own account
|
||||
if ($this->model->isLoggedIn() === true) {
|
||||
return 'account';
|
||||
}
|
||||
|
||||
return 'users/' . $this->model->id();
|
||||
}
|
||||
return 'users/' . $this->model->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns prepared data for the panel user picker
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
*/
|
||||
public function pickerData(array $params = null): array
|
||||
{
|
||||
$params['text'] ??= '{{ user.username }}';
|
||||
/**
|
||||
* Returns prepared data for the panel user picker
|
||||
*
|
||||
* @param array|null $params
|
||||
* @return array
|
||||
*/
|
||||
public function pickerData(array $params = null): array
|
||||
{
|
||||
$params['text'] ??= '{{ user.username }}';
|
||||
|
||||
return array_merge(parent::pickerData($params), [
|
||||
'email' => $this->model->email(),
|
||||
'username' => $this->model->username(),
|
||||
]);
|
||||
}
|
||||
return array_merge(parent::pickerData($params), [
|
||||
'email' => $this->model->email(),
|
||||
'username' => $this->model->username(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns navigation array with
|
||||
* previous and next user
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function prevNext(): array
|
||||
{
|
||||
$user = $this->model;
|
||||
/**
|
||||
* Returns navigation array with
|
||||
* previous and next user
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function prevNext(): array
|
||||
{
|
||||
$user = $this->model;
|
||||
|
||||
return [
|
||||
'next' => fn () => $this->toPrevNextLink($user->next(), 'username'),
|
||||
'prev' => fn () => $this->toPrevNextLink($user->prev(), 'username')
|
||||
];
|
||||
}
|
||||
return [
|
||||
'next' => fn () => $this->toPrevNextLink($user->next(), 'username'),
|
||||
'prev' => fn () => $this->toPrevNextLink($user->prev(), 'username')
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data array for the
|
||||
* view's component props
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function props(): array
|
||||
{
|
||||
$user = $this->model;
|
||||
$account = $user->isLoggedIn();
|
||||
$avatar = $user->avatar();
|
||||
/**
|
||||
* Returns the data array for the
|
||||
* view's component props
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function props(): array
|
||||
{
|
||||
$user = $this->model;
|
||||
$account = $user->isLoggedIn();
|
||||
$avatar = $user->avatar();
|
||||
|
||||
return array_merge(
|
||||
parent::props(),
|
||||
$account ? [] : $this->prevNext(),
|
||||
[
|
||||
'blueprint' => $this->model->role()->name(),
|
||||
'model' => [
|
||||
'account' => $account,
|
||||
'avatar' => $avatar ? $avatar->url() : null,
|
||||
'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(),
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
return array_merge(
|
||||
parent::props(),
|
||||
$account ? [] : $this->prevNext(),
|
||||
[
|
||||
'blueprint' => $this->model->role()->name(),
|
||||
'model' => [
|
||||
'account' => $account,
|
||||
'avatar' => $avatar ? $avatar->url() : null,
|
||||
'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(),
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Translation object
|
||||
* for the selected Panel language
|
||||
*
|
||||
* @return \Kirby\Cms\Translation
|
||||
*/
|
||||
public function translation()
|
||||
{
|
||||
$kirby = $this->model->kirby();
|
||||
$lang = $this->model->language();
|
||||
return $kirby->translation($lang);
|
||||
}
|
||||
/**
|
||||
* Returns the Translation object
|
||||
* for the selected Panel language
|
||||
*
|
||||
* @return \Kirby\Cms\Translation
|
||||
*/
|
||||
public function translation()
|
||||
{
|
||||
$kirby = $this->model->kirby();
|
||||
$lang = $this->model->language();
|
||||
return $kirby->translation($lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data array for
|
||||
* this model's Panel view
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function view(): array
|
||||
{
|
||||
return [
|
||||
'breadcrumb' => $this->breadcrumb(),
|
||||
'component' => 'k-user-view',
|
||||
'props' => $this->props(),
|
||||
'title' => $this->model->username(),
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Returns the data array for
|
||||
* this model's Panel view
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function view(): array
|
||||
{
|
||||
return [
|
||||
'breadcrumb' => $this->breadcrumb(),
|
||||
'component' => 'k-user-view',
|
||||
'props' => $this->props(),
|
||||
'title' => $this->model->username(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
namespace Kirby\Panel;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Http\Response;
|
||||
use Kirby\Http\Url;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\I18n;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
|
@ -21,435 +22,434 @@ use Kirby\Toolkit\Str;
|
|||
*/
|
||||
class View
|
||||
{
|
||||
/**
|
||||
* Filters the data array based on headers or
|
||||
* query parameters. Requests can return only
|
||||
* certain data fields that way or globals can
|
||||
* be injected on demand.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public static function apply(array $data): array
|
||||
{
|
||||
$request = kirby()->request();
|
||||
$only = $request->header('X-Fiber-Only') ?? get('_only');
|
||||
/**
|
||||
* Filters the data array based on headers or
|
||||
* query parameters. Requests can return only
|
||||
* certain data fields that way or globals can
|
||||
* be injected on demand.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public static function apply(array $data): array
|
||||
{
|
||||
$request = App::instance()->request();
|
||||
$only = $request->header('X-Fiber-Only') ?? $request->get('_only');
|
||||
|
||||
if (empty($only) === false) {
|
||||
return static::applyOnly($data, $only);
|
||||
}
|
||||
if (empty($only) === false) {
|
||||
return static::applyOnly($data, $only);
|
||||
}
|
||||
|
||||
$globals = $request->header('X-Fiber-Globals') ?? get('_globals');
|
||||
$globals = $request->header('X-Fiber-Globals') ?? $request->get('_globals');
|
||||
|
||||
if (empty($globals) === false) {
|
||||
return static::applyGlobals($data, $globals);
|
||||
}
|
||||
if (empty($globals) === false) {
|
||||
return static::applyGlobals($data, $globals);
|
||||
}
|
||||
|
||||
return A::apply($data);
|
||||
}
|
||||
return A::apply($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if globals should be included in a JSON Fiber request. They are normally
|
||||
* only loaded with the full document request, but sometimes need to be updated.
|
||||
*
|
||||
* A global request can be activated with the `X-Fiber-Globals` header or the
|
||||
* `_globals` query parameter.
|
||||
*
|
||||
* @param array $data
|
||||
* @param string|null $globals
|
||||
* @return array
|
||||
*/
|
||||
public static function applyGlobals(array $data, ?string $globals = null): array
|
||||
{
|
||||
// split globals string into an array of fields
|
||||
$globalKeys = Str::split($globals, ',');
|
||||
/**
|
||||
* Checks if globals should be included in a JSON Fiber request. They are normally
|
||||
* only loaded with the full document request, but sometimes need to be updated.
|
||||
*
|
||||
* A global request can be activated with the `X-Fiber-Globals` header or the
|
||||
* `_globals` query parameter.
|
||||
*
|
||||
* @param array $data
|
||||
* @param string|null $globals
|
||||
* @return array
|
||||
*/
|
||||
public static function applyGlobals(array $data, ?string $globals = null): array
|
||||
{
|
||||
// split globals string into an array of fields
|
||||
$globalKeys = Str::split($globals, ',');
|
||||
|
||||
// add requested globals
|
||||
if (empty($globalKeys) === true) {
|
||||
return $data;
|
||||
}
|
||||
// add requested globals
|
||||
if (empty($globalKeys) === true) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$globals = static::globals();
|
||||
$globals = static::globals();
|
||||
|
||||
foreach ($globalKeys as $globalKey) {
|
||||
if (isset($globals[$globalKey]) === true) {
|
||||
$data[$globalKey] = $globals[$globalKey];
|
||||
}
|
||||
}
|
||||
foreach ($globalKeys as $globalKey) {
|
||||
if (isset($globals[$globalKey]) === true) {
|
||||
$data[$globalKey] = $globals[$globalKey];
|
||||
}
|
||||
}
|
||||
|
||||
// merge with shared data
|
||||
return A::apply($data);
|
||||
}
|
||||
// merge with shared data
|
||||
return A::apply($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the request should only return a limited
|
||||
* set of data. This can be activated with the `X-Fiber-Only`
|
||||
* header or the `_only` query parameter in a request.
|
||||
*
|
||||
* Such requests can fetch shared data or globals.
|
||||
* Globals will be loaded on demand.
|
||||
*
|
||||
* @param array $data
|
||||
* @param string|null $only
|
||||
* @return array
|
||||
*/
|
||||
public static function applyOnly(array $data, ?string $only = null): array
|
||||
{
|
||||
// split include string into an array of fields
|
||||
$onlyKeys = Str::split($only, ',');
|
||||
/**
|
||||
* Checks if the request should only return a limited
|
||||
* set of data. This can be activated with the `X-Fiber-Only`
|
||||
* header or the `_only` query parameter in a request.
|
||||
*
|
||||
* Such requests can fetch shared data or globals.
|
||||
* Globals will be loaded on demand.
|
||||
*
|
||||
* @param array $data
|
||||
* @param string|null $only
|
||||
* @return array
|
||||
*/
|
||||
public static function applyOnly(array $data, ?string $only = null): array
|
||||
{
|
||||
// split include string into an array of fields
|
||||
$onlyKeys = Str::split($only, ',');
|
||||
|
||||
// if a full request is made, return all data
|
||||
if (empty($onlyKeys) === true) {
|
||||
return $data;
|
||||
}
|
||||
// if a full request is made, return all data
|
||||
if (empty($onlyKeys) === true) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// otherwise filter data based on
|
||||
// dot notation, e.g. `$props.tab.columns`
|
||||
$result = [];
|
||||
// otherwise filter data based on
|
||||
// dot notation, e.g. `$props.tab.columns`
|
||||
$result = [];
|
||||
|
||||
// check if globals are requested and need to be merged
|
||||
if (Str::contains($only, '$')) {
|
||||
$data = array_merge_recursive(static::globals(), $data);
|
||||
}
|
||||
// check if globals are requested and need to be merged
|
||||
if (Str::contains($only, '$')) {
|
||||
$data = array_merge_recursive(static::globals(), $data);
|
||||
}
|
||||
|
||||
// make sure the data is already resolved to make
|
||||
// nested data fetching work
|
||||
$data = A::apply($data);
|
||||
// make sure the data is already resolved to make
|
||||
// nested data fetching work
|
||||
$data = A::apply($data);
|
||||
|
||||
// build a new array with all requested data
|
||||
foreach ($onlyKeys as $onlyKey) {
|
||||
$result[$onlyKey] = A::get($data, $onlyKey);
|
||||
}
|
||||
// build a new array with all requested data
|
||||
foreach ($onlyKeys as $onlyKey) {
|
||||
$result[$onlyKey] = A::get($data, $onlyKey);
|
||||
}
|
||||
|
||||
// Nest dotted keys in array but ignore $translation
|
||||
return A::nest($result, [
|
||||
'$translation'
|
||||
]);
|
||||
}
|
||||
// Nest dotted keys in array but ignore $translation
|
||||
return A::nest($result, [
|
||||
'$translation'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the shared data array for the individual views
|
||||
* The full shared data is always sent on every JSON and
|
||||
* full document request unless the `X-Fiber-Only` header or
|
||||
* the `_only` query parameter is set.
|
||||
*
|
||||
* @param array $view
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public static function data(array $view = [], array $options = []): array
|
||||
{
|
||||
$kirby = kirby();
|
||||
/**
|
||||
* Creates the shared data array for the individual views
|
||||
* The full shared data is always sent on every JSON and
|
||||
* full document request unless the `X-Fiber-Only` header or
|
||||
* the `_only` query parameter is set.
|
||||
*
|
||||
* @param array $view
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public static function data(array $view = [], array $options = []): array
|
||||
{
|
||||
$kirby = App::instance();
|
||||
|
||||
// multilang setup check
|
||||
$multilang = Panel::multilang();
|
||||
// multilang setup check
|
||||
$multilang = Panel::multilang();
|
||||
|
||||
// get the authenticated user
|
||||
$user = $kirby->user();
|
||||
// get the authenticated user
|
||||
$user = $kirby->user();
|
||||
|
||||
// user permissions
|
||||
$permissions = $user ? $user->role()->permissions()->toArray() : [];
|
||||
// user permissions
|
||||
$permissions = $user ? $user->role()->permissions()->toArray() : [];
|
||||
|
||||
// current content language
|
||||
$language = $kirby->language();
|
||||
// current content language
|
||||
$language = $kirby->language();
|
||||
|
||||
// shared data for all requests
|
||||
return [
|
||||
'$direction' => function () use ($kirby, $multilang, $language, $user) {
|
||||
if ($multilang === true && $language && $user) {
|
||||
$isDefault = $language->direction() === $kirby->defaultLanguage()->direction();
|
||||
$isFromUser = $language->code() === $user->language();
|
||||
// shared data for all requests
|
||||
return [
|
||||
'$direction' => function () use ($kirby, $multilang, $language, $user) {
|
||||
if ($multilang === true && $language && $user) {
|
||||
$isDefault = $language->direction() === $kirby->defaultLanguage()->direction();
|
||||
$isFromUser = $language->code() === $user->language();
|
||||
|
||||
if ($isDefault === false && $isFromUser === false) {
|
||||
return $language->direction();
|
||||
}
|
||||
}
|
||||
},
|
||||
'$language' => function () use ($kirby, $multilang, $language) {
|
||||
if ($multilang === true && $language) {
|
||||
return [
|
||||
'code' => $language->code(),
|
||||
'default' => $language->isDefault(),
|
||||
'direction' => $language->direction(),
|
||||
'name' => $language->name(),
|
||||
'rules' => $language->rules(),
|
||||
];
|
||||
}
|
||||
},
|
||||
'$languages' => function () use ($kirby, $multilang): array {
|
||||
if ($multilang === true) {
|
||||
return $kirby->languages()->values(fn ($language) => [
|
||||
'code' => $language->code(),
|
||||
'default' => $language->isDefault(),
|
||||
'direction' => $language->direction(),
|
||||
'name' => $language->name(),
|
||||
'rules' => $language->rules(),
|
||||
]);
|
||||
}
|
||||
if ($isDefault === false && $isFromUser === false) {
|
||||
return $language->direction();
|
||||
}
|
||||
}
|
||||
},
|
||||
'$language' => function () use ($kirby, $multilang, $language) {
|
||||
if ($multilang === true && $language) {
|
||||
return [
|
||||
'code' => $language->code(),
|
||||
'default' => $language->isDefault(),
|
||||
'direction' => $language->direction(),
|
||||
'name' => $language->name(),
|
||||
'rules' => $language->rules(),
|
||||
];
|
||||
}
|
||||
},
|
||||
'$languages' => function () use ($kirby, $multilang): array {
|
||||
if ($multilang === true) {
|
||||
return $kirby->languages()->values(fn ($language) => [
|
||||
'code' => $language->code(),
|
||||
'default' => $language->isDefault(),
|
||||
'direction' => $language->direction(),
|
||||
'name' => $language->name(),
|
||||
'rules' => $language->rules(),
|
||||
]);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
'$menu' => function () use ($options, $permissions) {
|
||||
return static::menu($options['areas'] ?? [], $permissions, $options['area']['id'] ?? null);
|
||||
},
|
||||
'$permissions' => $permissions,
|
||||
'$license' => (bool)$kirby->system()->license(),
|
||||
'$multilang' => $multilang,
|
||||
'$searches' => static::searches($options['areas'] ?? [], $permissions),
|
||||
'$url' => Url::current(),
|
||||
'$user' => function () use ($user) {
|
||||
if ($user) {
|
||||
return [
|
||||
'email' => $user->email(),
|
||||
'id' => $user->id(),
|
||||
'language' => $user->language(),
|
||||
'role' => $user->role()->id(),
|
||||
'username' => $user->username(),
|
||||
];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
'$menu' => function () use ($options, $permissions) {
|
||||
return static::menu($options['areas'] ?? [], $permissions, $options['area']['id'] ?? null);
|
||||
},
|
||||
'$permissions' => $permissions,
|
||||
'$license' => (bool)$kirby->system()->license(),
|
||||
'$multilang' => $multilang,
|
||||
'$searches' => static::searches($options['areas'] ?? [], $permissions),
|
||||
'$url' => $kirby->request()->url()->toString(),
|
||||
'$user' => function () use ($user) {
|
||||
if ($user) {
|
||||
return [
|
||||
'email' => $user->email(),
|
||||
'id' => $user->id(),
|
||||
'language' => $user->language(),
|
||||
'role' => $user->role()->id(),
|
||||
'username' => $user->username(),
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
'$view' => function () use ($kirby, $options, $view) {
|
||||
$defaults = [
|
||||
'breadcrumb' => [],
|
||||
'code' => 200,
|
||||
'path' => Str::after($kirby->path(), '/'),
|
||||
'timestamp' => (int)(microtime(true) * 1000),
|
||||
'props' => [],
|
||||
'search' => $kirby->option('panel.search.type', 'pages')
|
||||
];
|
||||
return null;
|
||||
},
|
||||
'$view' => function () use ($kirby, $options, $view) {
|
||||
$defaults = [
|
||||
'breadcrumb' => [],
|
||||
'code' => 200,
|
||||
'path' => Str::after($kirby->path(), '/'),
|
||||
'timestamp' => (int)(microtime(true) * 1000),
|
||||
'props' => [],
|
||||
'search' => $kirby->option('panel.search.type', 'pages')
|
||||
];
|
||||
|
||||
$view = array_replace_recursive($defaults, $options['area'] ?? [], $view);
|
||||
$view = array_replace_recursive($defaults, $options['area'] ?? [], $view);
|
||||
|
||||
// make sure that views and dialogs are gone
|
||||
unset(
|
||||
$view['dialogs'],
|
||||
$view['dropdowns'],
|
||||
$view['searches'],
|
||||
$view['views']
|
||||
);
|
||||
// make sure that views and dialogs are gone
|
||||
unset(
|
||||
$view['dialogs'],
|
||||
$view['dropdowns'],
|
||||
$view['searches'],
|
||||
$view['views']
|
||||
);
|
||||
|
||||
// resolve all callbacks in the view array
|
||||
return A::apply($view);
|
||||
}
|
||||
];
|
||||
}
|
||||
// resolve all callbacks in the view array
|
||||
return A::apply($view);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the error view with provided message
|
||||
*
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @return array
|
||||
*/
|
||||
public static function error(string $message, int $code = 404)
|
||||
{
|
||||
return [
|
||||
'code' => $code,
|
||||
'component' => 'k-error-view',
|
||||
'error' => $message,
|
||||
'props' => [
|
||||
'error' => $message,
|
||||
'layout' => Panel::hasAccess(kirby()->user()) ? 'inside' : 'outside'
|
||||
],
|
||||
'title' => 'Error'
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Renders the error view with provided message
|
||||
*
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @return array
|
||||
*/
|
||||
public static function error(string $message, int $code = 404)
|
||||
{
|
||||
return [
|
||||
'code' => $code,
|
||||
'component' => 'k-error-view',
|
||||
'error' => $message,
|
||||
'props' => [
|
||||
'error' => $message,
|
||||
'layout' => Panel::hasAccess(App::instance()->user()) ? 'inside' : 'outside'
|
||||
],
|
||||
'title' => 'Error'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates global data for the Panel.
|
||||
* This will be injected in the full Panel
|
||||
* view via the script tag. Global data
|
||||
* is only requested once on the first page load.
|
||||
* It can be loaded partially later if needed,
|
||||
* but is otherwise not included in Fiber calls.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function globals(): array
|
||||
{
|
||||
$kirby = kirby();
|
||||
/**
|
||||
* Creates global data for the Panel.
|
||||
* This will be injected in the full Panel
|
||||
* view via the script tag. Global data
|
||||
* is only requested once on the first page load.
|
||||
* It can be loaded partially later if needed,
|
||||
* but is otherwise not included in Fiber calls.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function globals(): array
|
||||
{
|
||||
$kirby = App::instance();
|
||||
|
||||
return [
|
||||
'$config' => function () use ($kirby) {
|
||||
return [
|
||||
'debug' => $kirby->option('debug', false),
|
||||
'kirbytext' => $kirby->option('panel.kirbytext', true),
|
||||
'search' => [
|
||||
'limit' => $kirby->option('panel.search.limit', 10),
|
||||
'type' => $kirby->option('panel.search.type', 'pages')
|
||||
],
|
||||
'translation' => $kirby->option('panel.language', 'en'),
|
||||
];
|
||||
},
|
||||
'$system' => function () use ($kirby) {
|
||||
$locales = [];
|
||||
return [
|
||||
'$config' => function () use ($kirby) {
|
||||
return [
|
||||
'debug' => $kirby->option('debug', false),
|
||||
'kirbytext' => $kirby->option('panel.kirbytext', true),
|
||||
'search' => [
|
||||
'limit' => $kirby->option('panel.search.limit', 10),
|
||||
'type' => $kirby->option('panel.search.type', 'pages')
|
||||
],
|
||||
'translation' => $kirby->option('panel.language', 'en'),
|
||||
];
|
||||
},
|
||||
'$system' => function () use ($kirby) {
|
||||
$locales = [];
|
||||
|
||||
foreach ($kirby->translations() as $translation) {
|
||||
$locales[$translation->code()] = $translation->locale();
|
||||
}
|
||||
foreach ($kirby->translations() as $translation) {
|
||||
$locales[$translation->code()] = $translation->locale();
|
||||
}
|
||||
|
||||
return [
|
||||
'ascii' => Str::$ascii,
|
||||
'csrf' => $kirby->auth()->csrfFromSession(),
|
||||
'isLocal' => $kirby->system()->isLocal(),
|
||||
'locales' => $locales,
|
||||
'slugs' => Str::$language,
|
||||
'title' => $kirby->site()->title()->or('Kirby Panel')->toString()
|
||||
];
|
||||
},
|
||||
'$translation' => function () use ($kirby) {
|
||||
if ($user = $kirby->user()) {
|
||||
$translation = $kirby->translation($user->language());
|
||||
} else {
|
||||
$translation = $kirby->translation($kirby->panelLanguage());
|
||||
}
|
||||
return [
|
||||
'ascii' => Str::$ascii,
|
||||
'csrf' => $kirby->auth()->csrfFromSession(),
|
||||
'isLocal' => $kirby->system()->isLocal(),
|
||||
'locales' => $locales,
|
||||
'slugs' => Str::$language,
|
||||
'title' => $kirby->site()->title()->or('Kirby Panel')->toString()
|
||||
];
|
||||
},
|
||||
'$translation' => function () use ($kirby) {
|
||||
if ($user = $kirby->user()) {
|
||||
$translation = $kirby->translation($user->language());
|
||||
} else {
|
||||
$translation = $kirby->translation($kirby->panelLanguage());
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => $translation->code(),
|
||||
'data' => $translation->dataWithFallback(),
|
||||
'direction' => $translation->direction(),
|
||||
'name' => $translation->name(),
|
||||
];
|
||||
},
|
||||
'$urls' => fn () => [
|
||||
'api' => $kirby->url('api'),
|
||||
'site' => $kirby->url('index')
|
||||
]
|
||||
];
|
||||
}
|
||||
return [
|
||||
'code' => $translation->code(),
|
||||
'data' => $translation->dataWithFallback(),
|
||||
'direction' => $translation->direction(),
|
||||
'name' => $translation->name(),
|
||||
];
|
||||
},
|
||||
'$urls' => fn () => [
|
||||
'api' => $kirby->url('api'),
|
||||
'site' => $kirby->url('index')
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the menu for the topbar
|
||||
*
|
||||
* @param array $areas
|
||||
* @param array $permissions
|
||||
* @param string|null $current
|
||||
* @return array
|
||||
*/
|
||||
public static function menu(?array $areas = [], ?array $permissions = [], ?string $current = null): array
|
||||
{
|
||||
$menu = [];
|
||||
/**
|
||||
* Creates the menu for the topbar
|
||||
*
|
||||
* @param array $areas
|
||||
* @param array $permissions
|
||||
* @param string|null $current
|
||||
* @return array
|
||||
*/
|
||||
public static function menu(?array $areas = [], ?array $permissions = [], ?string $current = null): array
|
||||
{
|
||||
$menu = [];
|
||||
|
||||
// areas
|
||||
foreach ($areas as $areaId => $area) {
|
||||
$access = $permissions['access'][$areaId] ?? true;
|
||||
// areas
|
||||
foreach ($areas as $areaId => $area) {
|
||||
$access = $permissions['access'][$areaId] ?? true;
|
||||
|
||||
// areas without access permissions get skipped entirely
|
||||
if ($access === false) {
|
||||
continue;
|
||||
}
|
||||
// areas without access permissions get skipped entirely
|
||||
if ($access === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// fetch custom menu settings from the area definition
|
||||
$menuSetting = $area['menu'] ?? false;
|
||||
// fetch custom menu settings from the area definition
|
||||
$menuSetting = $area['menu'] ?? false;
|
||||
|
||||
// menu settings can be a callback that can return true, false or disabled
|
||||
if (is_a($menuSetting, 'Closure') === true) {
|
||||
$menuSetting = $menuSetting($areas, $permissions, $current);
|
||||
}
|
||||
// menu settings can be a callback that can return true, false or disabled
|
||||
if (is_a($menuSetting, 'Closure') === true) {
|
||||
$menuSetting = $menuSetting($areas, $permissions, $current);
|
||||
}
|
||||
|
||||
// false will remove the area entirely just like with
|
||||
// disabled permissions
|
||||
if ($menuSetting === false) {
|
||||
continue;
|
||||
}
|
||||
// false will remove the area entirely just like with
|
||||
// disabled permissions
|
||||
if ($menuSetting === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$menu[] = [
|
||||
'current' => $areaId === $current,
|
||||
'disabled' => $menuSetting === 'disabled',
|
||||
'icon' => $area['icon'],
|
||||
'id' => $areaId,
|
||||
'link' => $area['link'],
|
||||
'text' => $area['label'],
|
||||
];
|
||||
}
|
||||
$menu[] = [
|
||||
'current' => $areaId === $current,
|
||||
'disabled' => $menuSetting === 'disabled',
|
||||
'icon' => $area['icon'],
|
||||
'id' => $areaId,
|
||||
'link' => $area['link'],
|
||||
'text' => $area['label'],
|
||||
];
|
||||
}
|
||||
|
||||
$menu[] = '-';
|
||||
$menu[] = [
|
||||
'current' => $current === 'account',
|
||||
'icon' => 'account',
|
||||
'id' => 'account',
|
||||
'link' => 'account',
|
||||
'disabled' => ($permissions['access']['account'] ?? false) === false,
|
||||
'text' => t('view.account'),
|
||||
];
|
||||
$menu[] = '-';
|
||||
$menu[] = '-';
|
||||
$menu[] = [
|
||||
'current' => $current === 'account',
|
||||
'icon' => 'account',
|
||||
'id' => 'account',
|
||||
'link' => 'account',
|
||||
'disabled' => ($permissions['access']['account'] ?? false) === false,
|
||||
'text' => I18n::translate('view.account'),
|
||||
];
|
||||
$menu[] = '-';
|
||||
|
||||
// logout
|
||||
$menu[] = [
|
||||
'icon' => 'logout',
|
||||
'id' => 'logout',
|
||||
'link' => 'logout',
|
||||
'text' => t('logout')
|
||||
];
|
||||
return $menu;
|
||||
}
|
||||
// logout
|
||||
$menu[] = [
|
||||
'icon' => 'logout',
|
||||
'id' => 'logout',
|
||||
'link' => 'logout',
|
||||
'text' => I18n::translate('logout')
|
||||
];
|
||||
return $menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the main panel view either as
|
||||
* JSON response or full HTML document based
|
||||
* on the request header or query params
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param array $options
|
||||
* @return \Kirby\Http\Response
|
||||
*/
|
||||
public static function response($data, array $options = [])
|
||||
{
|
||||
// handle redirects
|
||||
if (is_a($data, 'Kirby\Panel\Redirect') === true) {
|
||||
return Response::redirect($data->location(), $data->code());
|
||||
/**
|
||||
* Renders the main panel view either as
|
||||
* JSON response or full HTML document based
|
||||
* on the request header or query params
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param array $options
|
||||
* @return \Kirby\Http\Response
|
||||
*/
|
||||
public static function response($data, array $options = [])
|
||||
{
|
||||
// handle redirects
|
||||
if (is_a($data, 'Kirby\Panel\Redirect') === true) {
|
||||
return Response::redirect($data->location(), $data->code());
|
||||
|
||||
// handle Kirby exceptions
|
||||
} elseif (is_a($data, 'Kirby\Exception\Exception') === true) {
|
||||
$data = static::error($data->getMessage(), $data->getHttpCode());
|
||||
// handle Kirby exceptions
|
||||
} elseif (is_a($data, 'Kirby\Exception\Exception') === true) {
|
||||
$data = static::error($data->getMessage(), $data->getHttpCode());
|
||||
|
||||
// handle regular exceptions
|
||||
} elseif (is_a($data, 'Throwable') === true) {
|
||||
$data = static::error($data->getMessage(), 500);
|
||||
// handle regular exceptions
|
||||
} elseif (is_a($data, 'Throwable') === true) {
|
||||
$data = static::error($data->getMessage(), 500);
|
||||
|
||||
// only expect arrays from here on
|
||||
} elseif (is_array($data) === false) {
|
||||
$data = static::error('Invalid Panel response', 500);
|
||||
}
|
||||
// only expect arrays from here on
|
||||
} elseif (is_array($data) === false) {
|
||||
$data = static::error('Invalid Panel response', 500);
|
||||
}
|
||||
|
||||
// get all data for the request
|
||||
$fiber = static::data($data, $options);
|
||||
// get all data for the request
|
||||
$fiber = static::data($data, $options);
|
||||
|
||||
// if requested, send $fiber data as JSON
|
||||
if (Panel::isFiberRequest() === true) {
|
||||
// if requested, send $fiber data as JSON
|
||||
if (Panel::isFiberRequest() === true) {
|
||||
// filter data, if only or globals headers or
|
||||
// query parameters are set
|
||||
$fiber = static::apply($fiber);
|
||||
|
||||
// filter data, if only or globals headers or
|
||||
// query parameters are set
|
||||
$fiber = static::apply($fiber);
|
||||
return Panel::json($fiber, $fiber['$view']['code'] ?? 200);
|
||||
}
|
||||
|
||||
return Panel::json($fiber, $fiber['$view']['code'] ?? 200);
|
||||
}
|
||||
// load globals for the full document response
|
||||
$globals = static::globals();
|
||||
|
||||
// load globals for the full document response
|
||||
$globals = static::globals();
|
||||
// resolve and merge globals and shared data
|
||||
$fiber = array_merge_recursive(A::apply($globals), A::apply($fiber));
|
||||
|
||||
// resolve and merge globals and shared data
|
||||
$fiber = array_merge_recursive(A::apply($globals), A::apply($fiber));
|
||||
// render the full HTML document
|
||||
return Document::response($fiber);
|
||||
}
|
||||
|
||||
// render the full HTML document
|
||||
return Document::response($fiber);
|
||||
}
|
||||
public static function searches(array $areas, array $permissions)
|
||||
{
|
||||
$searches = [];
|
||||
|
||||
public static function searches(array $areas, array $permissions)
|
||||
{
|
||||
$searches = [];
|
||||
|
||||
foreach ($areas as $area) {
|
||||
foreach ($area['searches'] ?? [] as $id => $params) {
|
||||
$searches[$id] = [
|
||||
'icon' => $params['icon'] ?? 'search',
|
||||
'label' => $params['label'] ?? Str::ucfirst($id),
|
||||
'id' => $id
|
||||
];
|
||||
}
|
||||
}
|
||||
return $searches;
|
||||
}
|
||||
foreach ($areas as $area) {
|
||||
foreach ($area['searches'] ?? [] as $id => $params) {
|
||||
$searches[$id] = [
|
||||
'icon' => $params['icon'] ?? 'search',
|
||||
'label' => $params['label'] ?? Str::ucfirst($id),
|
||||
'id' => $id
|
||||
];
|
||||
}
|
||||
}
|
||||
return $searches;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue