first version

This commit is contained in:
Bastian Allgeier
2019-01-13 23:17:34 +01:00
commit 01277f79f2
595 changed files with 82913 additions and 0 deletions

446
kirby/src/Cms/System.php Executable file
View File

@@ -0,0 +1,446 @@
<?php
namespace Kirby\Cms;
use Throwable;
use Kirby\Data\Json;
use Kirby\Exception\Exception;
use Kirby\Exception\PermissionException;
use Kirby\Http\Remote;
use Kirby\Http\Uri;
use Kirby\Http\Url;
use Kirby\Toolkit\Dir;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
/**
* The System class gathers all information
* about the server, PHP and other environment
* parameters and checks for a valid setup.
*
* This is mostly used by the panel installer
* to check if the panel can be installed at all.
*/
class System
{
/**
* @var App
*/
protected $app;
/**
* @param 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();
}
/**
* 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(),
'server' => $this->server(),
];
}
/**
* Check for a writable accounts folder
*
* @return boolean
*/
public function accounts(): bool
{
return is_writable($this->app->root('accounts'));
}
/**
* Check for a writable content folder
*
* @return boolean
*/
public function content(): bool
{
return is_writable($this->app->root('content'));
}
/**
* Check for an existing curl extension
*
* @return boolean
*/
public function curl(): bool
{
return extension_loaded('curl');
}
/**
* Create the most important folders
* if they don't exist yet
*
* @return void
*/
public function init()
{
/* /site/accounts */
try {
Dir::make($this->app->root('accounts'));
} catch (Throwable $e) {
throw new PermissionException('The accounts directory could not be created');
}
/* /content */
try {
Dir::make($this->app->root('content'));
} catch (Throwable $e) {
throw new PermissionException('The content directory could not be created');
}
try {
Dir::make($this->app->root('media'));
} catch (Throwable $e) {
throw new PermissionException('The media directory could not be created');
}
}
/**
* 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 boolean
*/
public function isInstallable(): bool
{
return $this->isLocal() === true || $this->app->option('panel.install', false) === true;
}
/**
* Check if Kirby is already installed
*
* @return boolean
*/
public function isInstalled(): bool
{
return $this->app->users()->count() > 0;
}
/**
* Check if this is a local installation
*
* @return boolean
*/
public function isLocal(): bool
{
$server = $this->app->server();
$host = $server->host();
if ($host === 'localhost') {
return true;
}
if (in_array($server->address(), ['::1', '127.0.0.1', '0.0.0.0']) === true) {
return true;
}
if (Str::endsWith($host, '.dev') === true) {
return true;
}
if (Str::endsWith($host, '.local') === true) {
return true;
}
if (Str::endsWith($host, '.test') === true) {
return true;
}
return false;
}
/**
* Check if all tests pass
*
* @return boolean
*/
public function isOk(): bool
{
if ($this->isInstallable() === false) {
return false;
}
return in_array(false, array_values($this->status()), true) === false;
}
/**
* Returns the app's index URL for
* licensing purposes without scheme
*
* @return string
*/
protected function licenseUrl(): string
{
$url = $this->app->url('index');
if (Url::isAbsolute($url)) {
$uri = Url::toObject($url);
} else {
// index URL was configured without host, use the current host
$uri = Uri::current([
'path' => $url,
'query' => null
]);
}
return $uri->setScheme(null)->setSlash(false)->toString();
}
/**
* 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 licenseUrlNormalized(string $url = null): string
{
if ($url === null) {
$url = $this->licenseUrl();
}
// 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;
}
/**
* Loads the license file and returns
* the license information if available
*
* @return string|false
*/
public function license()
{
try {
$license = Json::read($this->app->root('config') . '/.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->licenseUrlNormalized() !== $this->licenseUrlNormalized($license['domain'])) {
return false;
}
return $license['license'];
}
/**
* Check for an existing mbstring extension
*
* @return boolean
*/
public function mbString(): bool
{
return extension_loaded('mbstring');
}
/**
* Check for a writable media folder
*
* @return boolean
*/
public function media(): bool
{
return is_writable($this->app->root('media'));
}
/**
* Check for a valid PHP version
*
* @return boolean
*/
public function php(): bool
{
return version_compare(phpversion(), '7.1.0', '>');
}
/**
* Validates the license key
* and adds it to the .license file in the config
* folder if possible.
*
* @param string $license
* @param string $email
* @return boolean
*/
public function register(string $license, string $email): bool
{
$response = Remote::get('https://licenses.getkirby.com/register', [
'data' => [
'license' => $license,
'email' => $email,
'domain' => $this->licenseUrl()
]
]);
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('config') . '/.license';
// save the license information
Json::write($file, $json);
if ($this->license() === false) {
throw new Exception('The license could not be verified');
}
return true;
}
/**
* Check for a valid server environment
*
* @return boolean
*/
public function server(): bool
{
$servers = [
'apache',
'caddy',
'litespeed',
'nginx',
'php'
];
$software = $_SERVER['SERVER_SOFTWARE'] ?? null;
return preg_match('!(' . implode('|', $servers) . ')!i', $software) > 0;
}
/**
* Check for a writable sessions folder
*
* @return boolean
*/
public function sessions(): bool
{
return is_writable($this->app->root('sessions'));
}
/**
* Return the status as array
*
* @return array
*/
public function toArray(): array
{
return $this->status();
}
/**
* Upgrade to the new folder separator
*
* @param string $root
* @return void
*/
public static function upgradeContent(string $root)
{
$index = Dir::read($root);
foreach ($index as $dir) {
$oldRoot = $root . '/' . $dir;
$newRoot = preg_replace('!\/([0-9]+)\-!', '/$1_', $oldRoot);
if (is_dir($oldRoot) === true) {
Dir::move($oldRoot, $newRoot);
static::upgradeContent($newRoot);
}
}
}
}