Upgrade to 3.1.3

This commit is contained in:
Bastian Allgeier
2019-04-23 16:13:26 +02:00
parent eb29ef6d6c
commit 066913cb6e
53 changed files with 606 additions and 261 deletions

View File

@@ -538,7 +538,7 @@ class Api
'file' => ltrim($e->getFile(), $_SERVER['DOCUMENT_ROOT'] ?? null),
'line' => $e->getLine(),
'code' => empty($e->getCode()) === false ? $e->getCode() : 500,
'route' => $this->route->pattern()
'route' => $this->route ? $this->route->pattern() : null
];
}
}

View File

@@ -2,6 +2,8 @@
namespace Kirby\Cache;
use APCUIterator;
/**
* APCu Cache Driver
*
@@ -14,6 +16,53 @@ namespace Kirby\Cache;
class ApcuCache extends Cache
{
/**
* Checks if the current key exists in cache
*
* @param string $key
* @return boolean
*/
public function exists(string $key): bool
{
return apcu_exists($this->key($key));
}
/**
* Flush the entire cache directory
*
* @return boolean
*/
public function flush(): bool
{
if (empty($this->options['prefix']) === false) {
return apcu_delete(new APCUIterator('!^' . preg_quote($this->options['prefix']) . '!'));
} else {
return apcu_clear_cache();
}
}
/**
* Remove an item from the cache
*
* @param string $key
* @return boolean
*/
public function remove(string $key): bool
{
return apcu_delete($this->key($key));
}
/**
* Retrieve an item from the cache.
*
* @param string $key
* @return mixed
*/
public function retrieve(string $key)
{
return Value::fromJson(apcu_fetch($this->key($key)));
}
/**
* Write an item to the cache for a given number of minutes.
*
@@ -29,49 +78,6 @@ class ApcuCache extends Cache
*/
public function set(string $key, $value, int $minutes = 0)
{
return apcu_store($key, $this->value($value, $minutes)->toJson(), $this->expiration($minutes));
}
/**
* Retrieve an item from the cache.
*
* @param string $key
* @return mixed
*/
public function retrieve(string $key)
{
return Value::fromJson(apcu_fetch($key));
}
/**
* Checks if the current key exists in cache
*
* @param string $key
* @return boolean
*/
public function exists(string $key): bool
{
return apcu_exists($key);
}
/**
* Remove an item from the cache
*
* @param string $key
* @return boolean
*/
public function remove(string $key): bool
{
return apcu_delete($key);
}
/**
* Flush the entire cache directory
*
* @return boolean
*/
public function flush(): bool
{
return apcu_clear_cache();
return apcu_store($this->key($key), $this->value($value, $minutes)->toJson(), $this->expiration($minutes));
}
}

View File

@@ -52,6 +52,21 @@ class Cache
return null;
}
/**
* Adds the prefix to the key if given
*
* @param string $key
* @return string
*/
protected function key(string $key): string
{
if (empty($this->options['prefix']) === false) {
$key = $this->options['prefix'] . '/' . $key;
}
return $key;
}
/**
* Private method to retrieve the cache value
* This needs to be defined by the driver

View File

@@ -35,11 +35,6 @@ class FileCache extends Cache
// try to create the directory
Dir::make($this->options['root'], true);
// check for a valid cache directory
if (is_dir($this->options['root']) === false) {
throw new Exception('The cache directory does not exist');
}
}
/**
@@ -51,7 +46,8 @@ class FileCache extends Cache
protected function file(string $key): string
{
$extension = isset($this->options['extension']) ? '.' . $this->options['extension'] : '';
return $this->options['root'] . '/' . $key . $extension;
return $this->options['root'] . '/' . $this->key($key) . $extension;
}
/**
@@ -117,7 +113,13 @@ class FileCache extends Cache
*/
public function flush(): bool
{
if (Dir::remove($this->options['root']) === true && Dir::make($this->options['root']) === true) {
$root = $this->options['root'];
if (empty($this->options['prefix']) === false) {
$root = $root . '/' . $this->options['prefix'];
}
if (Dir::remove($root) === true && Dir::make($root) === true) {
return true;
}

View File

@@ -232,6 +232,33 @@ class App
return $this;
}
/**
* Returns all available blueprints for this installation
*
* @param string $type
* @return array
*/
public function blueprints(string $type = 'pages')
{
$blueprints = [];
foreach ($this->extensions('blueprints') as $name => $blueprint) {
if (dirname($name) === $type) {
$name = basename($name);
$blueprints[$name] = $name;
}
}
foreach (glob($this->root('blueprints') . '/' . $type . '/*.yml') as $blueprint) {
$name = F::name($blueprint);
$blueprints[$name] = $name;
}
ksort($blueprints);
return array_values($blueprints);
}
/**
* Calls any Kirby route
*
@@ -406,7 +433,7 @@ class App
$visitor = $this->visitor();
foreach ($visitor->acceptedLanguages() as $lang) {
if ($language = $languages->findBy('locale', $lang->locale())) {
if ($language = $languages->findBy('locale', $lang->locale(LC_ALL))) {
return $language;
}
}
@@ -1156,9 +1183,11 @@ class App
public function trigger(string $name, ...$arguments)
{
if ($functions = $this->extension('hooks', $name)) {
static $level = 0;
static $triggered = [];
$level++;
foreach ($functions as $function) {
foreach ($functions as $index => $function) {
if (in_array($function, $triggered[$name] ?? []) === true) {
continue;
}
@@ -1169,6 +1198,12 @@ class App
// bind the App object to the hook
$function->call($this, ...$arguments);
}
$level--;
if ($level === 0) {
$triggered = [];
}
}
}
@@ -1204,6 +1239,16 @@ class App
return static::$version = static::$version ?? Data::read(static::$root . '/composer.json')['version'] ?? null;
}
/**
* Creates a hash of the version number
*
* @return string
*/
public static function versionHash(): string
{
return md5(static::version());
}
/**
* Returns the visitor object
*

View File

@@ -82,8 +82,13 @@ class ContentTranslation
*/
public function content(): array
{
$parent = $this->parent();
$content = $this->content ?? $parent->readContent($this->code());
$parent = $this->parent();
if ($this->content === null) {
$this->content = $parent->readContent($this->code());
}
$content = $this->content;
// merge with the default content
if ($this->isDefault() === false && $defaultLanguage = $parent->kirby()->defaultLanguage()) {

View File

@@ -21,7 +21,8 @@ class Form extends BaseForm
if ($kirby->multilang() === true) {
$fields = $props['fields'] ?? [];
$isDefaultLanguage = $kirby->language()->isDefault();
$languageCode = $props['language'] ?? $kirby->language()->code();
$isDefaultLanguage = $languageCode === $kirby->defaultLanguage()->code();
foreach ($fields as $fieldName => $fieldProps) {
// switch untranslatable fields to readonly
@@ -40,7 +41,7 @@ class Form extends BaseForm
public static function for(Model $model, array $props = [])
{
// get the original model data
$original = $model->content()->toArray();
$original = $model->content($props['language'] ?? null)->toArray();
$values = $props['values'] ?? [];
// convert closures to values

View File

@@ -46,7 +46,7 @@ class Language extends Model
protected $direction;
/**
* @var string
* @var array
*/
protected $locale;
@@ -293,13 +293,18 @@ class Language extends Model
}
/**
* Returns the PHP locale setting string
* Returns the PHP locale setting array
*
* @return string
* @param int $category If passed, returns the locale for the specified category (e.g. LC_ALL) as string
* @return array|string
*/
public function locale(): string
public function locale(int $category = null)
{
return $this->locale;
if ($category !== null) {
return $this->locale[$category] ?? $this->locale[LC_ALL] ?? null;
} else {
return $this->locale;
}
}
/**
@@ -401,12 +406,21 @@ class Language extends Model
}
/**
* @param string $locale
* @param string|array $locale
* @return self
*/
protected function setLocale(string $locale = null): self
protected function setLocale($locale = null): self
{
$this->locale = $locale ?? $this->code;
if (is_array($locale)) {
$this->locale = $locale;
} elseif (is_string($locale)) {
$this->locale = [LC_ALL => $locale];
} elseif ($locale === null) {
$this->locale = [LC_ALL => $this->code];
} else {
throw new InvalidArgumentException('Locale must be string or array');
}
return $this;
}

View File

@@ -67,7 +67,7 @@ class Languages extends Collection
$files = glob(App::instance()->root('languages') . '/*.php');
foreach ($files as $file) {
$props = include_once $file;
$props = include $file;
if (is_array($props) === true) {

View File

@@ -405,15 +405,16 @@ abstract class ModelWithContent extends Model
* Updates the model data
*
* @param array $input
* @param string $language
* @param string $languageCode
* @param boolean $validate
* @return self
*/
public function update(array $input = null, string $languageCode = null, bool $validate = false)
{
$form = Form::for($this, [
'input' => $input,
'ignoreDisabled' => $validate === false,
'input' => $input,
'language' => $languageCode,
]);
// validate the input

View File

@@ -30,7 +30,7 @@ class Panel
{
$mediaRoot = $kirby->root('media') . '/panel';
$panelRoot = $kirby->root('panel') . '/dist';
$versionHash = md5($kirby->version());
$versionHash = $kirby->versionHash();
$versionRoot = $mediaRoot . '/' . $versionHash;
// check if the version already exists
@@ -72,12 +72,15 @@ class Panel
// get the uri object for the panel url
$uri = new Uri($url = $kirby->url('panel'));
$pluginCss = new PanelPlugins('css');
$pluginJs = new PanelPlugins('js');
$view = new View($kirby->root('kirby') . '/views/panel.php', [
'kirby' => $kirby,
'config' => $kirby->option('panel'),
'assetUrl' => $kirby->url('media') . '/panel/' . md5($kirby->version()),
'pluginCss' => $kirby->url('media') . '/plugins/index.css',
'pluginJs' => $kirby->url('media') . '/plugins/index.js',
'assetUrl' => $kirby->url('media') . '/panel/' . $kirby->versionHash(),
'pluginCss' => $pluginCss->url(),
'pluginJs' => $pluginJs->url(),
'icons' => F::read($kirby->root('panel') . '/dist/img/icons.svg'),
'panelUrl' => $uri->path()->toString(true) . '/',
'options' => [

232
kirby/src/Cms/PanelPlugins.php Executable file
View File

@@ -0,0 +1,232 @@
<?php
namespace Kirby\Cms;
use Kirby\Toolkit\Dir;
use Kirby\Toolkit\F;
/**
* The PanelPlugins class takes care of collecting
* js and css plugin files for the panel and caches
* them in the media folder
*/
class PanelPlugins
{
/**
* Cache of all collected plugin files
*
* @var array
*/
public $files;
/**
* Cache of the unique plugin hash for the url and root
*
* @var string
*/
public $hash;
/**
* css or js
*
* @var string
*/
public $type;
/**
* Creates a new panel plugin instance by type (css or js)
*
* @param string $type
*/
public function __construct(string $type)
{
$this->type = $type;
}
/**
* Collects and returns the plugin files for all plugins
*
* @return array
*/
public function files(): array
{
if ($this->files !== null) {
return $this->files;
}
$this->files = [];
foreach (App::instance()->plugins() as $plugin) {
$file = $plugin->root() . '/index.' . $this->type;
if (file_exists($file) === true) {
$this->files[] = $file;
}
}
return $this->files;
}
/**
* Checks if the cache exists
*
* @return boolean
*/
public function exist(): bool
{
return file_exists($this->root());
}
/**
* Returns the path to the cache folder
*
* @return string
*/
public function folder(): string
{
return 'panel/' . App::versionHash() . '/plugins/' . $this->type;
}
/**
* Collects and removes garbage from old plugin versions
*
* @return boolean
*/
public function gc(): bool
{
$folder = App::instance()->root('media') . '/' . $this->folder();
foreach (glob($folder . '/*') as $dir) {
$name = basename($dir);
if ($name !== $this->hash()) {
Dir::remove($dir);
}
}
return true;
}
/**
* Returns the unique hash for the cache file
* The hash is generated from all plugin filenames
* and the max modification date to make sure changes
* will always be cached properly
*
* @return string
*/
public function hash(): string
{
if ($this->hash !== null) {
return $this->hash;
}
return $this->hash = $this->id() . '-' . $this->modified();
}
/**
* Returns a unique id based on all
* plugin file roots
*
* @return string
*/
public function id(): string
{
return crc32(implode(array_values($this->files())));
}
/**
* 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);
}
return max($modified);
}
/**
* Returns the full path to the cache file
* This is used for the root and url methods
*
* @return string
*/
public function path(): string
{
return $this->folder() . '/' . $this->hash() . '/index.' . $this->type;
}
/**
* Read the files from all plugins and concatenate them
*
* @return string
*/
public function read(): string
{
$dist = [];
foreach ($this->files() as $file) {
$dist[] = file_get_contents($file);
}
return implode(PHP_EOL, $dist);
}
/**
* Checks if the cache exists and
* otherwise (re)creates it
*
* @return boolean
*/
public function publish(): bool
{
if ($this->exist() === true) {
return true;
}
$this->write();
$this->gc();
return true;
}
/**
* Absolute path to the cache file
*
* @return string
*/
public function root(): string
{
return App::instance()->root('media') . '/' . $this->path();
}
/**
* Absolute url to the cache file
* This is used by the panel to link the plugins
*
* @return string
*/
public function url(): string
{
return App::instance()->url('media') . '/' . $this->path();
}
/**
* Creates the cache file
*
* @return boolean
*/
public function write(): bool
{
return F::write($this->root(), $this->read());
}
}

View File

@@ -13,9 +13,10 @@ class Permissions
{
protected $actions = [
'access' => [
'panel' => true,
'users' => true,
'site' => true
'panel' => true,
'settings' => true,
'site' => true,
'users' => true,
],
'files' => [
'changeName' => true,

View File

@@ -13,49 +13,6 @@ use Kirby\Toolkit\F;
*/
class PluginAssets
{
/**
* Concatenate all plugin js and css files into
* a single file and copy them to /media/plugins/index.css or /media/plugins/index.js
*
* @param string $extension
* @return string
*/
public static function index(string $extension): string
{
$kirby = App::instance();
$cache = $kirby->root('media') . '/plugins/.index.' . $extension;
$build = false;
$modified = [0];
$assets = [];
foreach ($kirby->plugins() as $plugin) {
$file = $plugin->root() . '/index.' . $extension;
if (file_exists($file) === true) {
$assets[] = $file;
$modified[] = F::modified($file);
}
}
if (empty($assets)) {
return false;
}
if (file_exists($cache) === false || filemtime($cache) < max($modified)) {
$dist = [];
foreach ($assets as $asset) {
$dist[] = file_get_contents($asset);
}
$dist = implode(PHP_EOL, $dist);
F::write($cache, $dist);
} else {
$dist = file_get_contents($cache);
}
return $dist;
}
/**
* Clean old/deprecated assets on every resolve
*
@@ -105,6 +62,9 @@ class PluginAssets
$target = $plugin->mediaRoot() . '/' . $filename;
$url = $plugin->mediaUrl() . '/' . $filename;
// create the plugin directory first
Dir::make($plugin->mediaRoot(), true);
if (F::link($source, $target, 'symlink') === true) {
return Response::redirect($url);
}

View File

@@ -284,9 +284,7 @@ trait UserActions
*/
protected function writeCredentials(array $credentials): bool
{
$export = '<?php' . PHP_EOL . PHP_EOL . 'return ' . var_export($credentials, true) . ';';
return F::write($this->root() . '/index.php', $export);
return Data::write($this->root() . '/index.php', $credentials);
}
/**

View File

@@ -82,6 +82,11 @@ class PHP extends Handler
$php = static::encode($data);
$php = "<?php\n\nreturn $php;";
return F::write($file, $php);
if (F::write($file, $php) === true) {
F::invalidateOpcodeCache($file);
return true;
}
return false;
}
}

View File

@@ -56,6 +56,8 @@ class Yaml extends Handler
return $yaml;
}
// remove bom
$yaml = str_replace("\xEF\xBB\xBF", '', $yaml);
$result = Spyc::YAMLLoadString($yaml);
if (is_array($result)) {

View File

@@ -352,7 +352,7 @@ class FileSessionStore extends SessionStore
foreach ($iterator as $file) {
// make sure that the file is a session file
// prevents deleting files like .gitignore or other unrelated files
if (preg_match('/[0-9]+\.[a-z0-9]+\.sess/', $file->getFilename()) !== 1) {
if (preg_match('/^[0-9]+\.[a-z0-9]+\.sess$/', $file->getFilename()) !== 1) {
continue;
}
@@ -364,9 +364,6 @@ class FileSessionStore extends SessionStore
if ($expiryTime < $currentTime) {
// the session has expired, delete it
$this->destroy($expiryTime, $id);
} else {
// the following files are all going to be still valid
break;
}
}
}

View File

@@ -57,8 +57,12 @@ class A
* returned if no element has been found
* @return mixed
*/
public static function get(array $array, $key, $default = null)
public static function get($array, $key, $default = null)
{
if (is_array($array) === false) {
return $array;
}
// return the entire array if the key is null
if ($key === null) {
return $array;
@@ -79,17 +83,28 @@ class A
// support dot notation
if (strpos($key, '.') !== false) {
$keys = explode('.', $key);
$keys = explode('.', $key);
$firstKey = array_shift($keys);
foreach ($keys as $innerKey) {
if (isset($array[$innerKey]) === false) {
return $default;
if (isset($array[$firstKey]) === false) {
$currentKey = $firstKey;
while ($innerKey = array_shift($keys)) {
$currentKey = $currentKey . '.' . $innerKey;
if (isset($array[$currentKey]) === true) {
return static::get($array[$currentKey], implode('.', $keys), $default);
}
}
$array = $array[$innerKey];
return $default;
}
return $array;
if (is_array($array[$firstKey]) === true) {
return static::get($array[$firstKey], implode('.', $keys), $default);
}
return $default;
}
return $default;

View File

@@ -27,12 +27,7 @@ class Controller
foreach ($info->getParameters() as $parameter) {
$name = $parameter->getName();
if (isset($data[$name]) === false) {
throw new Exception(sprintf('The "%s" parameter is missing', $name));
}
$args[] = $data[$name];
$args[] = $data[$name] ?? null;
}
return $args;

View File

@@ -264,6 +264,21 @@ class F
return pathinfo($name, PATHINFO_BASENAME);
}
/**
* Invalidate opcode cache for file.
*
* @param string $file The path of the file
* @return boolean
*/
public static function invalidateOpcodeCache(string $file): bool
{
if (function_exists('opcache_invalidate') && strlen(ini_get('opcache.restrict_api')) === 0) {
return opcache_invalidate($file, true);
} else {
return false;
}
}
/**
* Checks if a file is of a certain type
*