1
0

downgrade to kirby v3

This commit is contained in:
Philip Wagner
2024-09-01 10:47:15 +02:00
parent a4b2aece7b
commit af86acb7a1
1085 changed files with 54743 additions and 65042 deletions

View File

@@ -2,12 +2,16 @@
namespace Kirby\Cms;
use Kirby\Cms\System\UpdateStatus;
use Kirby\Data\Json;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\PermissionException;
use Kirby\Filesystem\Dir;
use Kirby\Filesystem\F;
use Kirby\Http\Remote;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\V;
use Throwable;
/**
@@ -26,38 +30,60 @@ use Throwable;
*/
class System
{
// cache
protected License|null $license = null;
protected UpdateStatus|null $updateStatus = null;
/**
* @var \Kirby\Cms\App
*/
protected $app;
public function __construct(protected App $app)
/**
* @param \Kirby\Cms\App $app
*/
public function __construct(App $app)
{
$this->app = $app;
// try to create all folders that could be missing
$this->init();
}
/**
* Improved `var_dump` output
*
* @return array
*/
public function __debugInfo(): array
{
return $this->toArray();
}
/**
* Check for a writable accounts folder
*
* @return bool
*/
public function accounts(): bool
{
return is_writable($this->app->root('accounts')) === true;
return is_writable($this->app->root('accounts'));
}
/**
* Check for a writable content folder
*
* @return bool
*/
public function content(): bool
{
return is_writable($this->app->root('content')) === true;
return is_writable($this->app->root('content'));
}
/**
* Check for an existing curl extension
*
* @return bool
*/
public function curl(): bool
{
return extension_loaded('curl') === true;
return extension_loaded('curl');
}
/**
@@ -66,8 +92,9 @@ class System
* root. Otherwise it will return null.
*
* @param string $folder 'git', 'content', 'site', 'kirby'
* @return string|null
*/
public function exposedFileUrl(string $folder): string|null
public function exposedFileUrl(string $folder): ?string
{
if (!$url = $this->folderUrl($folder)) {
return null;
@@ -75,10 +102,7 @@ class System
switch ($folder) {
case 'content':
return $url . '/' . basename($this->app->site()->storage()->contentFile(
'published',
'default'
));
return $url . '/' . basename($this->app->site()->contentFile());
case 'git':
return $url . '/config';
case 'kirby':
@@ -114,20 +138,19 @@ class System
* root. Otherwise it will return null.
*
* @param string $folder 'git', 'content', 'site', 'kirby'
* @return string|null
*/
public function folderUrl(string $folder): string|null
public function folderUrl(string $folder): ?string
{
$index = $this->app->root('index');
$root = match ($folder) {
'git' => $index . '/.git',
default => $this->app->root($folder)
};
if (
$root === null ||
is_dir($root) === false ||
is_dir($index) === false
) {
if ($folder === 'git') {
$root = $index . '/.git';
} else {
$root = $this->app->root($folder);
}
if ($root === null || is_dir($root) === false || is_dir($index) === false) {
return null;
}
@@ -153,103 +176,69 @@ class System
/**
* Returns the app's human-readable
* index URL without scheme
*
* @return string
*/
public function indexUrl(): string
{
return $this->app->url('index', true)
->setScheme(null)
->setSlash(false)
->toString();
}
/**
* Returns an array with relevant system information
* used for debugging
* @since 4.3.0
*/
public function info(): array
{
return [
'kirby' => $this->app->version(),
'php' => phpversion(),
'server' => $this->serverSoftware(),
'license' => $this->license()->label(),
'languages' => $this->app->languages()->values(
fn ($lang) => $lang->code()
)
];
return $this->app->url('index', true)->setScheme(null)->setSlash(false)->toString();
}
/**
* Create the most important folders
* if they don't exist yet
*
* @return void
* @throws \Kirby\Exception\PermissionException
*/
public function init(): void
public function init()
{
// init /site/accounts
try {
Dir::make($this->app->root('accounts'));
} catch (Throwable) {
} catch (Throwable $e) {
throw new PermissionException('The accounts directory could not be created');
}
// init /site/sessions
try {
Dir::make($this->app->root('sessions'));
} catch (Throwable) {
} catch (Throwable $e) {
throw new PermissionException('The sessions directory could not be created');
}
// init /content
try {
Dir::make($this->app->root('content'));
} catch (Throwable) {
} catch (Throwable $e) {
throw new PermissionException('The content directory could not be created');
}
// init /media
try {
Dir::make($this->app->root('media'));
} catch (Throwable) {
} catch (Throwable $e) {
throw new PermissionException('The media directory could not be created');
}
}
/**
* Check if the Panel has 2FA activated
*/
public function is2FA(): bool
{
return ($this->loginMethods()['password']['2fa'] ?? null) === true;
}
/**
* Check if the Panel has 2FA with TOTP activated
*/
public function is2FAWithTOTP(): bool
{
return
$this->is2FA() === true &&
in_array('totp', $this->app->auth()->enabledChallenges()) === true;
}
/**
* Check if the Panel is installable.
* Check if the panel is installable.
* On a public server the panel.install
* option must be explicitly set to true
* to get the installer up and running.
*
* @return bool
*/
public function isInstallable(): bool
{
return
$this->isLocal() === true ||
$this->app->option('panel.install', false) === true;
return $this->isLocal() === true || $this->app->option('panel.install', false) === true;
}
/**
* Check if Kirby is already installed
*
* @return bool
*/
public function isInstalled(): bool
{
@@ -258,6 +247,8 @@ class System
/**
* Check if this is a local installation
*
* @return bool
*/
public function isLocal(): bool
{
@@ -266,6 +257,8 @@ class System
/**
* Check if all tests pass
*
* @return bool
*/
public function isOk(): bool
{
@@ -275,16 +268,108 @@ class System
/**
* Loads the license file and returns
* the license information if available
*
* @return string|bool License key or `false` if the current user has
* permissions for access.settings, otherwise just a
* boolean that tells whether a valid license is active
*/
public function license(): License
public function license()
{
return $this->license ??= License::read();
try {
$license = Json::read($this->app->root('license'));
} catch (Throwable $e) {
return false;
}
// check for all required fields for the validation
if (isset(
$license['license'],
$license['order'],
$license['date'],
$license['email'],
$license['domain'],
$license['signature']
) !== true) {
return false;
}
// build the license verification data
$data = [
'license' => $license['license'],
'order' => $license['order'],
'email' => hash('sha256', $license['email'] . 'kwAHMLyLPBnHEskzH9pPbJsBxQhKXZnX'),
'domain' => $license['domain'],
'date' => $license['date']
];
// get the public key
$pubKey = F::read($this->app->root('kirby') . '/kirby.pub');
// verify the license signature
if (openssl_verify(json_encode($data), hex2bin($license['signature']), $pubKey, 'RSA-SHA256') !== 1) {
return false;
}
// verify the URL
if ($this->licenseUrl() !== $this->licenseUrl($license['domain'])) {
return false;
}
// only return the actual license key if the
// current user has appropriate permissions
$user = $this->app->user();
if ($user && $user->isAdmin() === true) {
return $license['license'];
} else {
return true;
}
}
/**
* Normalizes the app's index URL for
* licensing purposes
*
* @param string|null $url Input URL, by default the app's index URL
* @return string Normalized URL
*/
protected function licenseUrl(string $url = null): string
{
if ($url === null) {
$url = $this->indexUrl();
}
// remove common "testing" subdomains as well as www.
// to ensure that installations of the same site have
// the same license URL; only for installations at /,
// subdirectory installations are difficult to normalize
if (Str::contains($url, '/') === false) {
if (Str::startsWith($url, 'www.')) {
return substr($url, 4);
}
if (Str::startsWith($url, 'dev.')) {
return substr($url, 4);
}
if (Str::startsWith($url, 'test.')) {
return substr($url, 5);
}
if (Str::startsWith($url, 'staging.')) {
return substr($url, 8);
}
}
return $url;
}
/**
* Returns the configured UI modes for the login form
* with their respective options
*
* @return array
*
* @throws \Kirby\Exception\InvalidArgumentException If the configuration is invalid
* (only in debug mode)
*/
@@ -343,38 +428,45 @@ class System
/**
* Check for an existing mbstring extension
*
* @return bool
*/
public function mbString(): bool
{
return extension_loaded('mbstring') === true;
return extension_loaded('mbstring');
}
/**
* Check for a writable media folder
*
* @return bool
*/
public function media(): bool
{
return is_writable($this->app->root('media')) === true;
return is_writable($this->app->root('media'));
}
/**
* Check for a valid PHP version
*
* @return bool
*/
public function php(): bool
{
return
version_compare(PHP_VERSION, '8.1.0', '>=') === true &&
version_compare(PHP_VERSION, '8.4.0', '<') === true;
version_compare(PHP_VERSION, '7.4.0', '>=') === true &&
version_compare(PHP_VERSION, '8.2.0', '<') === true;
}
/**
* Returns a sorted collection of all
* installed plugins
*
* @return \Kirby\Cms\Collection
*/
public function plugins(): Collection
public function plugins()
{
$plugins = new Collection($this->app->plugins());
return $plugins->sortBy('name', 'asc');
return (new Collection(App::instance()->plugins()))->sortBy('name', 'asc');
}
/**
@@ -382,50 +474,123 @@ class System
* and adds it to the .license file in the config
* folder if possible.
*
* @param string|null $license
* @param string|null $email
* @return bool
* @throws \Kirby\Exception\Exception
* @throws \Kirby\Exception\InvalidArgumentException
*/
public function register(string $license = null, string $email = null): bool
{
$license = new License(
code: $license,
domain: $this->indexUrl(),
email: $email,
);
if (Str::startsWith($license, 'K3-PRO-') === false) {
throw new InvalidArgumentException([
'key' => 'license.format'
]);
}
if (V::email($email) === false) {
throw new InvalidArgumentException([
'key' => 'license.email'
]);
}
// @codeCoverageIgnoreStart
$response = Remote::get('https://hub.getkirby.com/register', [
'data' => [
'license' => $license,
'email' => Str::lower(trim($email)),
'domain' => $this->indexUrl()
]
]);
if ($response->code() !== 200) {
throw new Exception($response->content());
}
// decode the response
$json = Json::decode($response->content());
// replace the email with the plaintext version
$json['email'] = $email;
// where to store the license file
$file = $this->app->root('license');
// save the license information
Json::write($file, $json);
if ($this->license() === false) {
throw new InvalidArgumentException([
'key' => 'license.verification'
]);
}
// @codeCoverageIgnoreEnd
$this->license = $license->register();
return true;
}
/**
* Returns the detected server software
* Check for a valid server environment
*
* @return bool
*/
public function serverSoftware(): string
public function server(): bool
{
return $this->app->environment()->get('SERVER_SOFTWARE', '');
return $this->serverSoftware() !== null;
}
/**
* Returns the detected server software
*
* @return string|null
*/
public function serverSoftware(): ?string
{
if ($servers = $this->app->option('servers')) {
$servers = A::wrap($servers);
} else {
$servers = [
'apache',
'caddy',
'litespeed',
'nginx',
'php'
];
}
$software = $this->app->environment()->get('SERVER_SOFTWARE', '');
preg_match('!(' . implode('|', $servers) . ')!i', $software, $matches);
return $matches[0] ?? null;
}
/**
* Check for a writable sessions folder
*
* @return bool
*/
public function sessions(): bool
{
return is_writable($this->app->root('sessions')) === true;
return is_writable($this->app->root('sessions'));
}
/**
* Get an status array of all checks
*
* @return array
*/
public function status(): array
{
return [
'accounts' => $this->accounts(),
'content' => $this->content(),
'curl' => $this->curl(),
'sessions' => $this->sessions(),
'mbstring' => $this->mbstring(),
'media' => $this->media(),
'php' => $this->php()
'accounts' => $this->accounts(),
'content' => $this->content(),
'curl' => $this->curl(),
'sessions' => $this->sessions(),
'mbstring' => $this->mbstring(),
'media' => $this->media(),
'php' => $this->php(),
'server' => $this->server(),
];
}
@@ -433,56 +598,35 @@ class System
* Returns the site's title as defined in the
* content file or `site.yml` blueprint
* @since 3.6.0
*
* @return string
*/
public function title(): string
{
$site = $this->app->site();
if ($site->title()->isNotEmpty() === true) {
if ($site->title()->isNotEmpty()) {
return $site->title()->value();
}
return $site->blueprint()->title();
}
/**
* @return array
*/
public function toArray(): array
{
return $this->status();
}
/**
* Returns the update status object unless
* the update check for Kirby has been disabled
* @since 3.8.0
*
* @param array|null $data Custom override for the getkirby.com update data
*/
public function updateStatus(array|null $data = null): UpdateStatus|null
{
if ($this->updateStatus !== null) {
return $this->updateStatus;
}
$kirby = $this->app;
$option =
$kirby->option('updates.kirby') ??
$kirby->option('updates', true);
if ($option === false) {
return null;
}
return $this->updateStatus = new UpdateStatus(
$kirby,
$option === 'security',
$data
);
}
/**
* Upgrade to the new folder separator
*
* @param string $root
* @return void
*/
public static function upgradeContent(string $root): void
public static function upgradeContent(string $root)
{
$index = Dir::read($root);
@@ -496,13 +640,4 @@ class System
}
}
}
/**
* Improved `var_dump` output
* @codeCoverageIgnore
*/
public function __debugInfo(): array
{
return $this->toArray();
}
}