Initial xentral_oss_20.3.c9ffacf
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\DependencyInjection;
|
||||
|
||||
abstract class AbstractBaseContainer implements ContainerInterface
|
||||
{
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function has($name);
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
abstract public function get($name);
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function __invoke()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\DependencyInjection;
|
||||
|
||||
interface ContainerInterface
|
||||
{
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has($name);
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed|object
|
||||
*/
|
||||
public function get($name);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\DependencyInjection\Definition;
|
||||
|
||||
use Xentral\Core\DependencyInjection\Exception\InvalidArgumentException;
|
||||
|
||||
final class FactoryMethodDefinition
|
||||
{
|
||||
/** @var callable $callable */
|
||||
private $callable;
|
||||
|
||||
/** @var bool $shared Share the same instance? */
|
||||
private $shared;
|
||||
|
||||
/**
|
||||
* @param callable $callable
|
||||
* @param bool $shared
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct($callable, $shared = true)
|
||||
{
|
||||
if (!is_callable($callable, false)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Definition can\'t be created. "%s::%s" is not callable.', $callable[0], $callable[1]
|
||||
));
|
||||
}
|
||||
|
||||
$this->callable = $callable;
|
||||
$this->shared = (bool)$shared;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return callable
|
||||
*/
|
||||
public function getCallable()
|
||||
{
|
||||
return $this->callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isShared()
|
||||
{
|
||||
return $this->shared;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\DependencyInjection\Exception;
|
||||
|
||||
use Xentral\Core\Exception\CoreExceptionInterface;
|
||||
|
||||
interface ContainerExceptionInterface extends CoreExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\DependencyInjection\Exception;
|
||||
|
||||
use InvalidArgumentException as SplInvalidArgumentException;
|
||||
|
||||
class InvalidArgumentException extends SplInvalidArgumentException implements ContainerExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\DependencyInjection\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ServiceNotFoundException extends RuntimeException implements ContainerExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\DependencyInjection;
|
||||
|
||||
final class ServiceContainer extends AbstractBaseContainer
|
||||
{
|
||||
/** @var ServiceRegistry $registry */
|
||||
private $registry;
|
||||
|
||||
/**
|
||||
* @param ServiceRegistry $registry
|
||||
*/
|
||||
public function __construct(ServiceRegistry $registry)
|
||||
{
|
||||
$this->registry = $registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
return $this->registry->has($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed|object
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
return $this->registry->get($name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\DependencyInjection;
|
||||
|
||||
use Xentral\Components\Logger\LoggerAwareTrait;
|
||||
use Xentral\Core\DependencyInjection\Definition\FactoryMethodDefinition;
|
||||
use Xentral\Core\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Xentral\Core\DependencyInjection\Exception\ServiceNotFoundException;
|
||||
|
||||
final class ServiceRegistry extends AbstractBaseContainer
|
||||
{
|
||||
/** @var array $services Storage for service instances */
|
||||
private $services = [];
|
||||
|
||||
/** @var FactoryMethodDefinition[] $factories Storage for factory methods */
|
||||
private $factories = [];
|
||||
|
||||
/**
|
||||
* @param array $factoryMethods
|
||||
*/
|
||||
public function __construct(array $factoryMethods = [])
|
||||
{
|
||||
$this->addFactories($factoryMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
return $this->hasService($name) || $this->hasFactory($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @throws ServiceNotFoundException
|
||||
*
|
||||
* @return object|mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
if ($this->hasService($name)) {
|
||||
return $this->services[$name];
|
||||
}
|
||||
|
||||
if ($this->hasFactory($name)) {
|
||||
$definition = $this->factories[$name];
|
||||
$factoryMethod = $definition->getCallable();
|
||||
$serviceInstance = $factoryMethod($this->get('ServiceContainer'));
|
||||
//inject Logger if required
|
||||
if (
|
||||
$this->has('Logger')
|
||||
&& in_array(LoggerAwareTrait::class, class_uses($serviceInstance), true)
|
||||
) {
|
||||
/** @var LoggerAwareTrait $serviceInstance */
|
||||
$serviceInstance->setLogger($this->get('Logger'));
|
||||
}
|
||||
|
||||
// Don't save non-shared services
|
||||
if (!$definition->isShared()) {
|
||||
return $serviceInstance;
|
||||
}
|
||||
|
||||
// Save shared services
|
||||
$this->add($name, $serviceInstance);
|
||||
|
||||
return $this->services[$name];
|
||||
}
|
||||
|
||||
throw new ServiceNotFoundException(sprintf(
|
||||
'Service "%s" was not found.', $name
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param object $instance
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function add($name, $instance)
|
||||
{
|
||||
if (!is_object($instance)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'%s could not be added. Only objects can be added to container.', $name
|
||||
));
|
||||
}
|
||||
|
||||
$this->services[$name] = $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFactory($name)
|
||||
{
|
||||
return isset($this->factories[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param callable $callable
|
||||
* @param bool $shared
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function addFactory($name, $callable, $shared = true)
|
||||
{
|
||||
if (!is_callable($callable, true)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Factory "%s" can not be added. Second argument must be a callable.', $name
|
||||
));
|
||||
}
|
||||
|
||||
if (!is_string($name)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Factory "%s" can not be added. Factory name must be a string.', $name
|
||||
));
|
||||
}
|
||||
|
||||
if ($this->hasFactory($name)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Factory "%s" can not be added. Factory is already present.', $name
|
||||
));
|
||||
}
|
||||
|
||||
$this->factories[$name] = new FactoryMethodDefinition($callable, $shared);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|\Iterator $factories
|
||||
*/
|
||||
public function addFactories($factories)
|
||||
{
|
||||
foreach ($factories as $name => $callable) {
|
||||
$this->addFactory($name, $callable, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function hasService($name)
|
||||
{
|
||||
return isset($this->services[$name]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\ErrorHandler;
|
||||
|
||||
use Throwable;
|
||||
|
||||
final class ErrorHandler
|
||||
{
|
||||
/** @var array Error types that halts execution */
|
||||
const THROWABLE_ERROR_TYPES = [
|
||||
E_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_error.php */
|
||||
E_PARSE, /** @see http://www.bbminfo.com/Tutor/php_error_e_parse.php */
|
||||
E_CORE_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_core_error.php */
|
||||
E_COMPILE_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_compile_error.php */
|
||||
E_USER_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_user_error.php */
|
||||
E_RECOVERABLE_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_recoverable_error.php */
|
||||
];
|
||||
|
||||
/** @var string[] */
|
||||
private const DELETE_FILE_FOLDERS = [
|
||||
'www/pages',
|
||||
'www/lib/versandarten',
|
||||
'www/lib/zahlungsweisen',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
register_shutdown_function([$this, 'onShutdown']);
|
||||
|
||||
// Use own error output function
|
||||
ini_set('display_errors', true);
|
||||
set_error_handler([$this, 'handleError']);
|
||||
|
||||
set_exception_handler([$this, 'handleException']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function onShutdown()
|
||||
{
|
||||
$error = error_get_last();
|
||||
if ($error === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->isErrorTypeHaltingExecution((int)$error['type'])) {
|
||||
|
||||
// Try to free memory; in case of exhausted memory limit
|
||||
@gc_enable();
|
||||
@gc_collect_cycles();
|
||||
$this->handleError((int)$error['type'], $error['message'], $error['file'], $error['line']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $code
|
||||
* @param string $message
|
||||
* @param string $file
|
||||
* @param int $line
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handleError($code, $message, $file, $line)
|
||||
{
|
||||
if ($this->isErrorTypeHaltingExecution($code)) {
|
||||
$type = (string)$this->translateErrorType($code);
|
||||
$exception = new PhpErrorException(sprintf('%s: %s', $type, $message), (int)$code);
|
||||
$exception->setFile($file);
|
||||
$exception->setLine($line);
|
||||
$this->handleException($exception);
|
||||
die(); // Necessary for E_RECOVERABLE_ERROR
|
||||
}
|
||||
|
||||
return true; // Don't execute PHP internal error handler
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $exception
|
||||
*/
|
||||
public function handleException($exception)
|
||||
{
|
||||
$title = null;
|
||||
if ($this->isIoncubeError($exception)) {
|
||||
$title = $this->handleIoncubeError($exception);
|
||||
}
|
||||
|
||||
$data = new ErrorPageData($exception, $title);
|
||||
$renderer = new ErrorPageRenderer($data);
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
echo $renderer->renderErrorPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://secure.php.net/manual/en/errorfunc.constants.php
|
||||
*
|
||||
* @param int $type
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function translateErrorType($type)
|
||||
{
|
||||
$errors = [
|
||||
E_ERROR => 'Fatal Error',
|
||||
E_PARSE => 'Parse Error',
|
||||
E_CORE_ERROR => 'Core Error',
|
||||
E_COMPILE_ERROR => 'Compile Error',
|
||||
E_USER_ERROR => 'Fatal User Error',
|
||||
E_RECOVERABLE_ERROR => 'Recoverable Error',
|
||||
];
|
||||
|
||||
return $errors[(int)$type];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $type
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isErrorTypeHaltingExecution($type)
|
||||
{
|
||||
return in_array((int)$type, self::THROWABLE_ERROR_TYPES, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $exception
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isIoncubeError($exception)
|
||||
{
|
||||
if ((int)$exception->getCode() !== E_CORE_ERROR) {
|
||||
return false;
|
||||
}
|
||||
if (strpos($exception->getMessage(), 'requires a license file.') !== false) {
|
||||
return true;
|
||||
}
|
||||
if (strpos($exception->getMessage(), 'ionCube Encoder') !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $exception
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function handleIoncubeError($exception)
|
||||
{
|
||||
$file = $this->extractFileFromIoncubeError($exception);
|
||||
if (empty($file)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->isDeleteableFile($file)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@unlink($file);
|
||||
if(is_file($file)) {
|
||||
return sprintf('Es wurde eine alte Systemdatei gefunden die nicht manuell gelöscht werden konnte.
|
||||
Bitte löschen Sie die Datei %s', $file);
|
||||
}
|
||||
return 'Es wurde eine alte Systemdatei gefunden und automatisch gelöscht.
|
||||
Bitte führen Sie das Update nochmal durch dann sollte diese Meldung nicht mehr erscheinen.';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isDeleteableFile(string $file)
|
||||
{
|
||||
if (!is_file($file)) {
|
||||
return false;
|
||||
}
|
||||
$dir = dirname($file);
|
||||
foreach (self::DELETE_FILE_FOLDERS as $folder) {
|
||||
if (substr($dir, -strlen($folder)) === $folder) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @example "<br>The encoded file <b>/var/www/xentral/www/pages/adresse.php</b> requires a license file.<br>"
|
||||
* "The license file <b>/var/www/xentral/key.php</b> is corrupt."
|
||||
*
|
||||
* @param Throwable $exception
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function extractFileFromIoncubeError($exception)
|
||||
{
|
||||
$message = strip_tags($exception->getMessage());
|
||||
$theFilePos = stripos($message, 'The File ');
|
||||
if ($theFilePos === false) {
|
||||
$theFilePos = strpos($message, 'The encoded file');
|
||||
if ($theFilePos === false) {
|
||||
return null;
|
||||
}
|
||||
$theFilePos += 16;
|
||||
} else {
|
||||
$theFilePos += 9;
|
||||
}
|
||||
$file = trim(substr($message, $theFilePos));
|
||||
$file = explode(' ', $file);
|
||||
|
||||
return reset($file);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\ErrorHandler;
|
||||
|
||||
use JsonSerializable;
|
||||
use Throwable;
|
||||
|
||||
final class ErrorPageData implements JsonSerializable
|
||||
{
|
||||
/** @var Throwable $exception */
|
||||
private $exception;
|
||||
|
||||
/** @var string $title */
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* @param Throwable $exception
|
||||
* @param string|null $title
|
||||
*/
|
||||
public function __construct($exception, $title = null)
|
||||
{
|
||||
$this->exception = $exception;
|
||||
$this->title = !empty($title) ? (string)$title : 'Xentral: Es ist ein unerwarteter Fehler aufgetreten!';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return [
|
||||
'information' => $this->prepareSystemInformation(),
|
||||
'exception' => $this->prepareExceptionStack($this->exception),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Throwable
|
||||
*/
|
||||
public function getException()
|
||||
{
|
||||
return $this->exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $exception
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepareExceptionStack($exception)
|
||||
{
|
||||
$stack = [];
|
||||
$traces = $exception->getTrace();
|
||||
foreach ($traces as $index => $trace) {
|
||||
$stack[$index] = $trace;
|
||||
unset($stack[$index]['args']);
|
||||
}
|
||||
|
||||
return [
|
||||
'message' => $exception->getMessage(),
|
||||
'class' => get_class($exception),
|
||||
'code' => $exception->getCode(),
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'trace' => $stack,
|
||||
'previous' => $exception->getPrevious() !== null ? $this->prepareExceptionStack($exception->getPrevious()) : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function prepareSystemInformation()
|
||||
{
|
||||
return [
|
||||
'php' => [
|
||||
'general' => $this->getPhpGeneralInformations(),
|
||||
'settings' => $this->getPhpImportantSettings(),
|
||||
'extensions' => $this->getPhpExtensions(),
|
||||
],
|
||||
'software' => $this->getSoftwareInformations(),
|
||||
'env' => $this->getEnvironmentInformation(),
|
||||
'server' => $this->getServerInformation(),
|
||||
'request' => $this->getRequestInformation(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getEnvironmentInformation()
|
||||
{
|
||||
$scriptFile = isset($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : __FILE__;
|
||||
try {
|
||||
$fileOwner = !function_exists('posix_getpwuid')?null:@posix_getpwuid(@fileowner($scriptFile));
|
||||
} catch (Throwable $e) {
|
||||
$fileOwner = null;
|
||||
}
|
||||
try {
|
||||
$fileGroup = !function_exists('posix_getgrgid')?null:@posix_getgrgid(@filegroup($scriptFile));
|
||||
}
|
||||
catch (Throwable $e) {
|
||||
$fileGroup = null;
|
||||
}
|
||||
|
||||
return [
|
||||
'username' => !empty(@getenv('USER')) ? @getenv('USER') : @getenv('USERNAME'),
|
||||
'home_dir' => @getenv('HOME'),
|
||||
'document_root' => isset($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : null,
|
||||
'script_filename' => isset($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : null,
|
||||
'script_owner' => isset($fileOwner['name']) ? $fileOwner['name'] : null,
|
||||
'script_group' => isset($fileGroup['name']) ? $fileGroup['name'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getServerInformation()
|
||||
{
|
||||
return [
|
||||
'software' => isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : null,
|
||||
'signature' => isset($_SERVER['SERVER_SIGNATURE']) ? strip_tags(trim($_SERVER['SERVER_SIGNATURE'])) : null,
|
||||
'addr' => isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : null,
|
||||
'name' => isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null,
|
||||
'port' => isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getRequestInformation()
|
||||
{
|
||||
return [
|
||||
'is_https' => $this->isHttpsRequest(),
|
||||
'is_ajax' => $this->isAjaxRequest(),
|
||||
'time' => isset($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : null,
|
||||
'method' => isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : null,
|
||||
'scheme' => isset($_SERVER['REQUEST_SCHEME']) ? $_SERVER['REQUEST_SCHEME'] : null,
|
||||
'uri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : null,
|
||||
'referer' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null,
|
||||
'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getPhpGeneralInformations()
|
||||
{
|
||||
return [
|
||||
'version' => PHP_VERSION,
|
||||
'version_id' => PHP_VERSION_ID,
|
||||
'version_major' => PHP_MAJOR_VERSION,
|
||||
'version_minor' => PHP_MINOR_VERSION,
|
||||
'version_release' => PHP_RELEASE_VERSION,
|
||||
'server_api' => PHP_SAPI,
|
||||
'binary_dir' => PHP_BINDIR,
|
||||
'php_ini_dir' => php_ini_loaded_file(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getSoftwareInformations()
|
||||
{
|
||||
$version = '';
|
||||
$version_revision = '';
|
||||
$versionFile = dirname(dirname(dirname(__DIR__))) . '/version.php';
|
||||
if (is_file($versionFile)) {
|
||||
include $versionFile;
|
||||
}
|
||||
|
||||
/** @var string $version Defined in version.php */
|
||||
/** @var string $version_revision Defined in version.php */
|
||||
return [
|
||||
'xentral_version' => !empty($version) ? $version : null,
|
||||
'xentral_revision' => !empty($version_revision) ? $version_revision : null,
|
||||
'fpdf_version' => $this->getFpdfVersion(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getPhpImportantSettings()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'setting' => 'max_execution_time',
|
||||
'raw_value' => $this->getPhpMaxExecutionTimeValue(),
|
||||
'int_value' => (int)$this->getPhpMaxExecutionTimeValue(),
|
||||
],
|
||||
[
|
||||
'setting' => 'max_input_time',
|
||||
'raw_value' => @ini_get('max_input_time'),
|
||||
'int_value' => (int)@ini_get('max_input_time'),
|
||||
],
|
||||
[
|
||||
'setting' => 'post_max_size',
|
||||
'raw_value' => $this->getPostMaxSizeValue(),
|
||||
'int_value' => (int)$this->convertPhpValueToBytes($this->getPostMaxSizeValue()),
|
||||
],
|
||||
[
|
||||
'setting' => 'upload_max_filesize',
|
||||
'raw_value' => @ini_get('upload_max_filesize'),
|
||||
'int_value' => (int)$this->convertPhpValueToBytes(@ini_get('upload_max_filesize')),
|
||||
],
|
||||
[
|
||||
'setting' => 'memory_limit',
|
||||
'raw_value' => @ini_get('memory_limit'),
|
||||
'int_value' => (int)$this->convertPhpValueToBytes(@ini_get('memory_limit')),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function getPhpExtensions()
|
||||
{
|
||||
$extensionsLoaded = $this->getPhpExtensionsLoaded();
|
||||
$extensionsDefined = $this->getPhpExtensionsDefined();
|
||||
$extensionsOther = array_diff_key($extensionsLoaded, $extensionsDefined);
|
||||
ksort($extensionsOther);
|
||||
|
||||
return [
|
||||
'defined' => $this->getPhpExtensionsDefined(),
|
||||
'other' => $extensionsOther,
|
||||
];
|
||||
}
|
||||
|
||||
private function getPhpExtensionsLoaded()
|
||||
{
|
||||
$extensions = get_loaded_extensions();
|
||||
|
||||
return array_combine($extensions, array_fill(0, count($extensions), true));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getPhpExtensionsDefined()
|
||||
{
|
||||
$extensionsResult = [];
|
||||
$extensionsCheck = $this->getPhpExtensionsDefinedCallbacks();
|
||||
foreach ($extensionsCheck as $extension => $callback) {
|
||||
$checkResult = $callback();
|
||||
$extensionsResult[$extension] = $checkResult;
|
||||
}
|
||||
|
||||
return $extensionsResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getPhpExtensionsDefinedCallbacks()
|
||||
{
|
||||
return [
|
||||
'mysqli' => function () {
|
||||
return function_exists('mysqli_connect');
|
||||
},
|
||||
'mysqlnd' => function () {
|
||||
return extension_loaded('mysqlnd');
|
||||
},
|
||||
'PDO' => function () {
|
||||
return class_exists('\PDO');
|
||||
},
|
||||
'curl' => function () {
|
||||
return function_exists('curl_init');
|
||||
},
|
||||
'xml' => function () {
|
||||
return function_exists('simplexml_load_string');
|
||||
},
|
||||
'stream_socket_enable_crypto' => function () {
|
||||
return function_exists('stream_socket_enable_crypto');
|
||||
},
|
||||
'fsocket' => function () {
|
||||
return function_exists('fsockopen');
|
||||
},
|
||||
'openssl' => function () {
|
||||
return function_exists('openssl_error_string');
|
||||
},
|
||||
'mbstring' => function () {
|
||||
return function_exists('mb_encode_numericentity');
|
||||
},
|
||||
'json' => function () {
|
||||
return function_exists('json_encode');
|
||||
},
|
||||
'iconv' => function () {
|
||||
return function_exists('iconv');
|
||||
},
|
||||
'soap' => function () {
|
||||
return class_exists('\SoapClient');
|
||||
},
|
||||
'imap' => function () {
|
||||
return function_exists('imap_open');
|
||||
},
|
||||
'zip' => function () {
|
||||
return class_exists('\ZipArchive');
|
||||
},
|
||||
'gd' => function () {
|
||||
return function_exists('imagejpeg');
|
||||
},
|
||||
'ldap' => function () {
|
||||
return function_exists('ldap_connect');
|
||||
},
|
||||
'ioncube' => function () {
|
||||
if (!function_exists('ioncube_loader_version')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ioncubeMajorVersion = (int)@ioncube_loader_version();
|
||||
|
||||
return $ioncubeMajorVersion >= 5;
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function isHttpsRequest()
|
||||
{
|
||||
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
|
||||
return true;
|
||||
}
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] === 'on') {
|
||||
return true;
|
||||
}
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function isAjaxRequest()
|
||||
{
|
||||
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getPhpMaxExecutionTimeValue()
|
||||
{
|
||||
$maxExecutionTime = @ini_get('fastcgi_read_timeout'); // Nginx
|
||||
if (empty($maxExecutionTime)) {
|
||||
$maxExecutionTime = @ini_get('max_execution_time');
|
||||
}
|
||||
|
||||
return $maxExecutionTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getPostMaxSizeValue()
|
||||
{
|
||||
$postMaxSize = @ini_get('client_max_body_size');
|
||||
if (empty($postMaxSize)) {
|
||||
$postMaxSize = @ini_get('post_max_size');
|
||||
}
|
||||
|
||||
return $postMaxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
private function getFpdfVersion()
|
||||
{
|
||||
if (defined('USEFPDF3') && (bool)USEFPDF3 === true) {
|
||||
return 3;
|
||||
}
|
||||
if (defined('USEFPDF2') && (bool)USEFPDF2 === true) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts PHP size value to byte value; e.g. 64K => 65536 Bytes
|
||||
*
|
||||
* @param string $phpValue
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function convertPhpValueToBytes($phpValue)
|
||||
{
|
||||
$lastChar = strtoupper(substr(trim($phpValue), -1));
|
||||
switch ($lastChar) {
|
||||
case 'G':
|
||||
$bytes = (int)$phpValue * 1024 * 1024 * 1024;
|
||||
break;
|
||||
case 'M':
|
||||
$bytes = (int)$phpValue * 1024 * 1024;
|
||||
break;
|
||||
case 'K':
|
||||
$bytes = (int)$phpValue * 1024;
|
||||
break;
|
||||
default:
|
||||
$bytes = (int)$phpValue;
|
||||
}
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\ErrorHandler;
|
||||
|
||||
final class ErrorPageRenderer
|
||||
{
|
||||
/** @var array REQUIRED_PHP_EXTENSIONS */
|
||||
const REQUIRED_PHP_EXTENSIONS = [
|
||||
'PDO', 'mysqli', 'mysqlnd', 'mbstring', 'curl', 'xml', 'zip', 'stream_socket_enable_crypto'
|
||||
];
|
||||
|
||||
/** @var ErrorPageData $data */
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* @param ErrorPageData $data
|
||||
*/
|
||||
public function __construct(ErrorPageData $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function renderErrorPage()
|
||||
{
|
||||
$data = $this->data->getData();
|
||||
$title = $this->data->getTitle();
|
||||
$content = $this->renderPageHeader();
|
||||
|
||||
$content .= '<table>';
|
||||
$content .= '<tr><td colspan="2" id="headline">';
|
||||
$content .= '<h1>' . htmlspecialchars($title, ENT_QUOTES) . '</h1>';
|
||||
$content .= '<h2>' . htmlspecialchars($data['exception']['message'], ENT_QUOTES) . '</h2>';
|
||||
$content .= '</td></tr>';
|
||||
$content .= '<tr>';
|
||||
$content .= '<td width="20%" id="side">' . $this->renderInformationData($data['information']) . '</td>';
|
||||
$content .= '<td width="80%" id="main">' . $this->renderExceptionData($data['exception']) . '</td>';
|
||||
$content .= '</tr></table>';
|
||||
|
||||
$content .= $this->renderPageFooter();
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function renderPageHeader()
|
||||
{
|
||||
return <<<HTML
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Unerwarteter Fehler</title>
|
||||
<style type="text/css">
|
||||
html { padding: 0; margin: 0; }
|
||||
body { font-family: BlinkMacSystemFont, -apple-system, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 12px; line-height: 1.6em; color: #48494B; background-color: #EEE; padding: 0; margin: 0; }
|
||||
h1, h2, h3, h4, h5, h6 { padding: 0; margin: 0.5em 0 0.5em 0; font-weight: bold; }
|
||||
p { padding: 0; margin: 0 0 .25em 0; }
|
||||
a, a:link, a:visited, a:hover, a:active { text-decoration: none; }
|
||||
#headline { padding: 24px 12px 18px 12px; background-color: #42B8C5; }
|
||||
#headline h1 { color: #F5F5F5; font-size: 2rem; margin: 1rem 0; }
|
||||
#headline h2 { color: #9CD6DB; font-size: 1.1rem; font-weight: normal; margin: 1rem 0; }
|
||||
table { width: 100%; border-collapse: separate; border-spacing: 0; }
|
||||
table td, table th { text-align: left; padding: 10px 0 10px 0; vertical-align: baseline; }
|
||||
table th.head { padding: 5px 0 10px 0; background-color: #FFF; vertical-align: baseline; border-bottom: 2px solid #DBDBDB; }
|
||||
table th.head h3 { margin: 3px 0; }
|
||||
table td.trace { background-color: #F5F5F5; vertical-align: baseline; }
|
||||
table.exception { margin-bottom: 20px; border-top: 2px solid #DBDBDB; }
|
||||
table.exception td { border-bottom: 1px solid #DBDBDB; }
|
||||
td.stacktrace { padding-top: 0; padding-bottom: 0; background-color: #FFF; }
|
||||
td.stacktrace table { border-spacing: 0; }
|
||||
table.exception a:link code, table.exception a:visited code { color: #42B8C5; }
|
||||
table.exception a:hover code, table.exception a:active code { color: #2F9099; }
|
||||
td.stacktrace tr:last-child td { border: none; }
|
||||
#main { background-color: #FFF; padding: 2rem; }
|
||||
#side { min-width: 240px; padding: 5px 15px; background-color: #E9ECEF; }
|
||||
#side h1, #side h2, #side h3, #side h4, #side h5, #side h6 { color: #7A7A7A; font-weight: normal; text-transform: uppercase; margin: 1em 0 0.5em 0; }
|
||||
.float-right { float: right; }
|
||||
.separator { color: #999; }
|
||||
.classname { color: #42B8C5; }
|
||||
.namespace { }
|
||||
.method { }
|
||||
.number { display: inline-block; width: 20px; padding: 1px 6px; margin-right: 10px; text-align: center; background-color: #DBDBDB; border-radius: 5px; }
|
||||
.errorclass { font-weight: bold; }
|
||||
.errorfile { margin-left: 42px; }
|
||||
code { font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace, serif; font-size: 12px; font-weight: normal; color: #42B8C5; padding: 3px 4px 1px 4px; background-color: #E9ECEF; }
|
||||
code.success { color: #48494B; background-color: #9FF781; }
|
||||
code.warning { color: #48494B; background-color: #F4FA58; }
|
||||
code.error { color: #48494B; background-color: #FA5858; }
|
||||
span.success { background-color: #9FF781; padding: 0 1px; }
|
||||
span.warning { background-color: #F4FA58; padding: 0 1px; }
|
||||
span.error { background-color: #FA5858; padding: 0 1px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function renderPageFooter()
|
||||
{
|
||||
return '</body></html>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $exception
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function renderExceptionData($exception)
|
||||
{
|
||||
$content = '';
|
||||
$content .= '<h3>' . $this->renderExceptionHeadline($exception['class']) . '</h3>';
|
||||
$content .= '<table class="exception">';
|
||||
$content .= '<tr><th class="head">';
|
||||
$content .= '<h2>' . htmlspecialchars($exception['message'], ENT_QUOTES) . '</h2>';
|
||||
$content .= "<div><a href='editor://open?file={$exception['file']}&line={$exception['line']}'>";
|
||||
$content .= "<code>{$exception['file']}:{$exception['line']}</code>";
|
||||
$content .= '</div>';
|
||||
$content .= '</th></tr>';
|
||||
|
||||
if (!empty($exception['trace'])) {
|
||||
$content .= '<tr><td class="stacktrace">';
|
||||
$content .= $this->renderStackTrace($exception['trace']);
|
||||
$content .= '</td></tr>';
|
||||
}
|
||||
$content .= '</table>';
|
||||
|
||||
// Render previous exceptions at the end
|
||||
if ($exception['previous'] !== null) {
|
||||
$content .= $this->renderExceptionData($exception['previous']);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className Full-qualified class name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function renderExceptionHeadline($className)
|
||||
{
|
||||
$classNameParts = explode('\\', $className);
|
||||
$partsSize = count($classNameParts) - 1;
|
||||
|
||||
$headline = '';
|
||||
foreach ($classNameParts as $index => $part) {
|
||||
if ($index === $partsSize) {
|
||||
$headline .= "<span class='classname'>{$part}</span>";
|
||||
} else {
|
||||
$headline .= "<span class='namespace'>{$part}</span>";
|
||||
$headline .= " <small class='separator'>\</small> ";
|
||||
}
|
||||
}
|
||||
|
||||
return $headline;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $stackTrace
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function renderStackTrace($stackTrace)
|
||||
{
|
||||
$traceSize = count($stackTrace);
|
||||
|
||||
$content = '<table class="stacktrace">';
|
||||
foreach ($stackTrace as $index => $trace) {
|
||||
|
||||
// @todo Einkommentieren wenn ErrorHandler erprobt und stabil
|
||||
//if (isset($trace['class']) && $trace['class'] === ErrorHandler::class) {
|
||||
// continue;
|
||||
//}
|
||||
|
||||
$number = $traceSize - $index;
|
||||
$editorLink = 'editor://open?file=' . urlencode($trace['file']) . '&line=' . urlencode($trace['line']);
|
||||
$content .= '<tr>';
|
||||
$content .= '<td>';
|
||||
$content .= '<div class="errorclass">';
|
||||
$content .= "<span class='number'>{$number}</span>";
|
||||
$content .= "<span>{$trace['class']}</span>";
|
||||
$content .= "<span class='method'><span>→</span>{$trace['function']}()</span>";
|
||||
$content .= '</div>';
|
||||
if (!empty($trace['file'])) {
|
||||
$content .= "<div class='errorfile'><a href='{$editorLink}'>";
|
||||
$content .= "<code>{$trace['file']}:{$trace['line']}</code>";
|
||||
$content .= '</a></div>';
|
||||
}
|
||||
$content .= '</td>';
|
||||
$content .= '</tr>';
|
||||
}
|
||||
$content .= '</table>';
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function renderInformationData($data)
|
||||
{
|
||||
$content = "<h3>Systeminformationen</h3>\n";
|
||||
|
||||
$software = $data['software'];
|
||||
$content .= "<h4>Software</h4>\n";
|
||||
$content .= '<p>Xentral-Revision: <code>';
|
||||
$content .= !empty($software['xentral_revision']) ? $software['xentral_revision'] : '--';
|
||||
$content .= "</code></p>\n";
|
||||
$content .= '<p>Xentral-Version: <code>';
|
||||
$content .= !empty($software['xentral_version']) ? $software['xentral_version'] : '--';
|
||||
$content .= "</code>\n";
|
||||
$content .= '<p>FPDF-Version: <code>';
|
||||
$content .= !empty($software['fpdf_version']) ? $software['fpdf_version'] : '--';
|
||||
$content .= "</code>\n";
|
||||
|
||||
$general = $data['php']['general'];
|
||||
$content .= "<h4>PHP</h4>\n";
|
||||
$version = "{$general['version_major']}.{$general['version_minor']}.{$general['version_release']}";
|
||||
$content .= "<p>Version: <code>{$version}</code> ({$general['version']})</p>\n";
|
||||
$content .= "<p>Server-API: <code>{$general['server_api']}</code></p>\n";
|
||||
$content .= "<p>Binary-Pfad: <code>{$general['binary_dir']}</code></p>\n";
|
||||
$content .= "<p>php.ini: <code>{$general['php_ini_dir']}</code></p>\n";
|
||||
|
||||
$settings = $data['php']['settings'];
|
||||
$content .= "<h4>PHP-Einstellungen:</h4>\n";
|
||||
foreach ($settings as $setting) {
|
||||
switch ($setting['setting']) {
|
||||
case 'max_execution_time':
|
||||
$cssClass = $setting['int_value'] <= 0 || $setting['int_value'] >= 30 ? '' : 'warning';
|
||||
break;
|
||||
case 'max_input_time':
|
||||
$cssClass = $setting['int_value'] <= 0 || $setting['int_value'] >= 30 ? '' : 'warning';
|
||||
break;
|
||||
case 'post_max_size':
|
||||
$cssClass = $setting['int_value'] >= 8 * 1024 * 1024 ? '' : 'warning';
|
||||
break;
|
||||
case 'upload_max_filesize':
|
||||
$cssClass = $setting['int_value'] >= 8 * 1024 * 1024 ? '' : 'warning';
|
||||
break;
|
||||
case 'memory_limit':
|
||||
$cssClass = $setting['int_value'] >= 256 * 1024 * 1024 ? '' : 'warning';
|
||||
break;
|
||||
default:
|
||||
$cssClass = '';
|
||||
break;
|
||||
}
|
||||
$content .= sprintf(
|
||||
'<p><code class="%s">%s = %s</code></p>' . "\n",
|
||||
$cssClass, $setting['setting'], $setting['raw_value']
|
||||
);
|
||||
}
|
||||
|
||||
$content .= "<h4>PHP-Erweiterungen</h4>\n";
|
||||
|
||||
$defined = $data['php']['extensions']['defined'];
|
||||
$content .= '<h5>Benötigt</h5><p>';
|
||||
foreach ($defined as $extension => $isAvailable) {
|
||||
$failedCssClass = in_array($extension, self::REQUIRED_PHP_EXTENSIONS, true) ? 'error' : 'warning';
|
||||
$cssClass = $isAvailable === true ? '' : $failedCssClass;
|
||||
$content .= sprintf('<code class="%s">%s</code>', $cssClass, $extension) . ', ';
|
||||
}
|
||||
$content = substr_replace($content, '', -2);
|
||||
$content .= "</p>\n";
|
||||
|
||||
/*$other = $data['php']['extensions']['other'];
|
||||
$content .= "<h5>Sonstige</h5><p>";
|
||||
foreach ($other as $extension => $available) {
|
||||
$content .= sprintf('<code>%s</code>', $extension) . ', ';
|
||||
}
|
||||
$content = substr_replace($content, '', -2);
|
||||
$content .= "</p>\n";*/
|
||||
|
||||
$env = $data['env'];
|
||||
$content .= "<h4>Umgebung</h4>\n";
|
||||
$content .= "<p>Username: <code>{$env['username']}</code></p>\n";
|
||||
$content .= "<p>Home-Directory: <code>{$env['home_dir']}</code></p>\n";
|
||||
$content .= "<p>Document-Root: <code>{$env['document_root']}</code></p>\n";
|
||||
$content .= "<p>Script-Filename: <code>{$env['script_filename']}</code></p>\n";
|
||||
if ($env['script_owner'] !== null) {
|
||||
$content .= "<p>Script-Owner/-Group: <code>{$env['script_owner']}:{$env['script_group']}</code></p>\n";
|
||||
}
|
||||
|
||||
$server = $data['server'];
|
||||
$content .= "<h4>Webserver</h4>\n";
|
||||
$content .= '<p>Software: <code>' . (!empty($server['software']) ? $server['software'] : '--') . "</code></p>\n";
|
||||
$content .= '<p>Signatur: <code>' . (!empty($server['signature']) ? $server['signature'] : '--') . "</code></p>\n";
|
||||
$content .= "<p>Host: <code>{$server['name']}</code> (<code>{$server['addr']}:{$server['port']}</code>)</p>\n";
|
||||
|
||||
$request = $data['request'];
|
||||
$content .= "<h4>Request</h4>\n";
|
||||
$content .= "<p>Schema: <code>{$request['scheme']}</code></p>\n";
|
||||
$content .= "<p>Method/Uri: <code>{$request['method']} " . htmlspecialchars($request['uri']) . "</code></p>\n";
|
||||
$content .= '<p>Referer: <code>' . (!empty($request['referer']) ? htmlspecialchars($request['referer']) : '--') . "</code></p>\n";
|
||||
$content .= '<p>UserAgent: <code>' . (!empty($request['user_agent']) ? $request['user_agent'] : '--') . "</code></p>\n";
|
||||
$content .= '<p>AJAX-Request: <code>' . ($request['is_ajax'] === true ? 'true' : 'false') . "</code></p>\n";
|
||||
$content .= '<p>HTTPS-Request: <code>' . ($request['is_https'] === true ? 'true' : 'false') . "</code></p>\n";
|
||||
$content .= "<p>Timestamp: <code>{$request['time']}</code></p>\n";
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\ErrorHandler;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class PhpErrorException extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setFile($file)
|
||||
{
|
||||
$this->file = $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $line
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setLine($line)
|
||||
{
|
||||
$this->line = $line;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\Exception;
|
||||
|
||||
interface ComponentExceptionInterface extends XentralExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\Exception;
|
||||
|
||||
interface CoreExceptionInterface extends XentralExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\Exception;
|
||||
|
||||
interface ModuleExceptionInterface extends XentralExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\Exception;
|
||||
|
||||
interface WidgetExceptionInterface extends XentralExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
interface XentralExceptionInterface extends Throwable
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# Exceptions
|
||||
|
||||
Alle Exceptions im `classes`-Bereich implementieren das `XentralExceptionInterface`.
|
||||
|
||||
Jeder der vier Bereich hat ein eigenes `ExceptionInterface`. Jedes der Interfaces ist vom `XentralExceptionInterface`
|
||||
abgeleitet.
|
||||
|
||||
* Core > `CoreExceptionInterface`
|
||||
* Components > `ComponentExceptionInterface`
|
||||
* Modules > `ModuleExceptionInterface`
|
||||
* Widgets > `WidgetExceptionInterface`
|
||||
|
||||
Jedes Modul, jede Komponente und jedes Widget hat wiederum ein eigenes `ExceptionInterface`,
|
||||
z.b. das `HttpExceptionInterface` der Http-Komponente. Dieses Interface extended das entsprechende `ExceptionInterface`
|
||||
aus seinem Bereich.
|
||||
|
||||
Alle Exceptions in einem Modul/Komponente/Widget implementieren das `ExceptionInterface` des Moduls/Komponente/Widget.
|
||||
|
||||
Alle Exceptions sind von einer `SplException` abgeleitet, z.B.: `RuntimeException`
|
||||
|
||||
|
||||
###### Beispiel Exception-Baum
|
||||
|
||||
```
|
||||
Xentral\Core\Exception\XentralExceptionInterface
|
||||
└─ Xentral\Core\Exception\ComponentExceptionInterface
|
||||
└─ Xentral\Components\Http\Exception\HttpExceptionInterface
|
||||
└─ Xentral\Components\Http\Exception\MethodNotAllowedException
|
||||
└─ RuntimeException
|
||||
└─ Exception
|
||||
```
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\Installer;
|
||||
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RegexIterator;
|
||||
|
||||
/**
|
||||
* Scans recursively a directory and generates a class map for autoloading
|
||||
*/
|
||||
final class ClassMapGenerator
|
||||
{
|
||||
/** @var Psr4ClassNameResolver $resolver */
|
||||
private $resolver;
|
||||
|
||||
/** @var string $baseDir */
|
||||
private $baseDir;
|
||||
|
||||
/**
|
||||
* @param Psr4ClassNameResolver $resolver
|
||||
* @param string $baseDir Absolute path to installation folder
|
||||
*/
|
||||
public function __construct(Psr4ClassNameResolver $resolver, $baseDir)
|
||||
{
|
||||
$this->resolver = $resolver;
|
||||
$this->baseDir = $this->removeTrailingSlashFromDirectory($baseDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $scanDir Absolute path to directory
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function generate($scanDir)
|
||||
{
|
||||
if (!is_dir($scanDir)) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'"%s" is not a directory.', $scanDir
|
||||
));
|
||||
}
|
||||
|
||||
return $this->scanDir($scanDir);
|
||||
//return $this->prepareClassMap($classMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $scanDir Absolute path (without trailing slash)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function scanDir($scanDir)
|
||||
{
|
||||
$scanDir = $this->removeTrailingSlashFromDirectory($scanDir);
|
||||
|
||||
$directory = new RecursiveDirectoryIterator($scanDir);
|
||||
$iterator = new RecursiveIteratorIterator($directory);
|
||||
$matcher = new RegexIterator($iterator, '/^.+\.php$/', RegexIterator::MATCH);
|
||||
|
||||
$files = [];
|
||||
|
||||
/** @var \SplFileInfo $match */
|
||||
foreach ($matcher as $match) {
|
||||
$files[] = $match->getRealPath();
|
||||
}
|
||||
|
||||
$map = [];
|
||||
foreach ($files as $file) {
|
||||
$className = $this->resolver->resolveClassName($file);
|
||||
if ($className !== null) {
|
||||
$map[$className] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare file paths; make them relative to base dir
|
||||
*
|
||||
* @param array $classMap
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepareClassMap(array $classMap)
|
||||
{
|
||||
$prepared = [];
|
||||
|
||||
foreach ($classMap as $class => $file) {
|
||||
$relativePath = str_replace($this->baseDir, '', $file);
|
||||
$prepared[$class] = $relativePath;
|
||||
}
|
||||
|
||||
return $prepared;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dir
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function removeTrailingSlashFromDirectory($dir)
|
||||
{
|
||||
if (substr($dir, -1) === '/') {
|
||||
return substr_replace($dir, '', -1);
|
||||
}
|
||||
|
||||
return $dir;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\Installer;
|
||||
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RegexIterator;
|
||||
use RuntimeException;
|
||||
use Xentral\Components\SchemaCreator\Collection\SchemaCollection;
|
||||
|
||||
final class Installer
|
||||
{
|
||||
/** @var ClassMapGenerator $classMapGenerator */
|
||||
private $classMapGenerator;
|
||||
|
||||
/** @var Psr4ClassNameResolver $classNameResolver */
|
||||
private $classNameResolver;
|
||||
|
||||
/** @var array $classMap */
|
||||
private $classMap = [];
|
||||
|
||||
/** @var array $bootstrapClasses */
|
||||
private $bootstrapClasses = [];
|
||||
|
||||
/** @var array */
|
||||
private $services = [];
|
||||
|
||||
/** @var array $javascript */
|
||||
private $javascript = [];
|
||||
|
||||
/** @var string $classDir */
|
||||
private $classDir;
|
||||
|
||||
/**
|
||||
* @param ClassMapGenerator $generator
|
||||
*/
|
||||
public function __construct(ClassMapGenerator $generator, Psr4ClassNameResolver $resolver)
|
||||
{
|
||||
$this->classMapGenerator = $generator;
|
||||
$this->classNameResolver = $resolver;
|
||||
$this->classDir = dirname(dirname(__DIR__));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
if (!empty($this->classMap)) {
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
$this->classMap = $this->classMapGenerator->generate($this->classDir);
|
||||
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getServices()
|
||||
{
|
||||
if (!empty($this->services)) {
|
||||
return $this->services;
|
||||
}
|
||||
|
||||
$classNames = $this->getBootstrapClassNames();
|
||||
|
||||
foreach ($classNames as $className) {
|
||||
if (empty($className)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!class_exists($className, true)) {
|
||||
$this->loadClass($className);
|
||||
}
|
||||
if (!method_exists($className, 'registerServices')) {
|
||||
continue;
|
||||
}
|
||||
$services = forward_static_call([$className, 'registerServices']);
|
||||
foreach ($services as $serviceName => $factoryMethod) {
|
||||
$this->addServiceDefinition($serviceName, $className, $factoryMethod);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->services;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return SchemaCollection
|
||||
*/
|
||||
public function getTableSchemas(): SchemaCollection
|
||||
{
|
||||
$schemaCollection = new SchemaCollection();
|
||||
$classNames = $this->getBootstrapClassNames();
|
||||
|
||||
foreach ($classNames as $className) {
|
||||
if (empty($className)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!class_exists($className, true)) {
|
||||
$this->loadClass($className);
|
||||
}
|
||||
if (!method_exists($className, 'registerTableSchemas')) {
|
||||
continue;
|
||||
}
|
||||
forward_static_call([$className, 'registerTableSchemas'], $schemaCollection);
|
||||
}
|
||||
|
||||
return $schemaCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getJavascriptFiles()
|
||||
{
|
||||
$classNames = $this->getBootstrapClassNames();
|
||||
|
||||
foreach ($classNames as $className) {
|
||||
if (!class_exists($className, true)) {
|
||||
continue;
|
||||
}
|
||||
if (!method_exists($className, 'registerJavascript')) {
|
||||
continue;
|
||||
}
|
||||
$javascript = forward_static_call([$className, 'registerJavascript']);
|
||||
foreach ($javascript as $cacheName => $jsFiles) {
|
||||
$this->addJavascriptDefinition($cacheName, $jsFiles);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->javascript;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array Absolute paths to all bootstrap files
|
||||
*/
|
||||
private function getBootstrapFiles()
|
||||
{
|
||||
$directory = new RecursiveDirectoryIterator($this->classDir);
|
||||
$iterator = new RecursiveIteratorIterator($directory);
|
||||
$bootstraps = new RegexIterator($iterator, '/^.+Bootstrap\.php$/', RegexIterator::MATCH);
|
||||
|
||||
$files = [];
|
||||
|
||||
/** @var \SplFileInfo $bootstrap */
|
||||
foreach ($bootstraps as $bootstrap) {
|
||||
$files[] = $bootstrap->getRealPath();
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array FQCN of all bootstrap classes
|
||||
*/
|
||||
private function getBootstrapClassNames()
|
||||
{
|
||||
if (!empty($this->bootstrapClasses)) {
|
||||
return $this->bootstrapClasses;
|
||||
}
|
||||
|
||||
$files = $this->getBootstrapFiles();
|
||||
foreach ($files as $file) {
|
||||
$className = $this->classNameResolver->resolveClassName($file);
|
||||
if ($className === null) {
|
||||
continue;
|
||||
}
|
||||
$this->bootstrapClasses[] = $className;
|
||||
}
|
||||
|
||||
return $this->bootstrapClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $serviceName
|
||||
* @param string $bootstrapClass
|
||||
* @param string $factoryMethod
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function addServiceDefinition($serviceName, $bootstrapClass, $factoryMethod)
|
||||
{
|
||||
if (isset($this->services[$serviceName])) {
|
||||
$registeredCallString = $this->services[$serviceName][0] . '::' . $this->services[$serviceName][1];
|
||||
$failedCallString = $bootstrapClass . '::' . $factoryMethod;
|
||||
throw new RuntimeException(sprintf(
|
||||
'Service "%s" can not be registered. Name is already taken. Registered "%s" - Failed "%s"',
|
||||
$serviceName, $registeredCallString, $failedCallString
|
||||
));
|
||||
}
|
||||
|
||||
$this->services[$serviceName] = [$bootstrapClass, $factoryMethod];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $cacheName
|
||||
* @param $files
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function addJavascriptDefinition($cacheName, $files)
|
||||
{
|
||||
$this->javascript[$cacheName] = $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadClass($className)
|
||||
{
|
||||
if (empty($className)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($this->classMap)) {
|
||||
$this->getClassMap();
|
||||
}
|
||||
|
||||
if (!isset($this->classMap[$className])) {
|
||||
throw new RuntimeException(sprintf(
|
||||
'Could not load class "%s"', $className
|
||||
));
|
||||
}
|
||||
|
||||
include $this->classMap[$className];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\Installer;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
final class InstallerCacheConfig
|
||||
{
|
||||
/** @var string $userdataDir */
|
||||
private $userdataTempDir;
|
||||
|
||||
/**
|
||||
* @param string $userdataTempDir
|
||||
*/
|
||||
public function __construct($userdataTempDir)
|
||||
{
|
||||
$this->userdataTempDir = $userdataTempDir;
|
||||
if (!is_dir($userdataTempDir)) {
|
||||
$this->createUserDataTempDir();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUserDataTempDir()
|
||||
{
|
||||
return $this->userdataTempDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getClassMapCacheFile()
|
||||
{
|
||||
return $this->userdataTempDir . '/cache_classmap.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getServiceCacheFile()
|
||||
{
|
||||
return $this->userdataTempDir . '/cache_services.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getJavascriptCacheFile()
|
||||
{
|
||||
return $this->userdataTempDir . '/cache_javascript.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function createUserDataTempDir(): void
|
||||
{
|
||||
if (!mkdir($this->userdataTempDir, 0777, true) && !is_dir($this->userdataTempDir)) {
|
||||
throw new RuntimeException(sprintf(
|
||||
'Verzeichnis "%s" konnte nicht angelegt werden.', $this->userdataTempDir
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\Installer;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
final class InstallerCacheWriter
|
||||
{
|
||||
/** @var InstallerCacheConfig $config */
|
||||
private $config;
|
||||
|
||||
/** @var Installer $installer */
|
||||
private $installer;
|
||||
|
||||
/**
|
||||
* @param InstallerCacheConfig $config
|
||||
* @param Installer $installer
|
||||
*/
|
||||
public function __construct(InstallerCacheConfig $config, Installer $installer)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->installer = $installer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Wird momentan nicht verwendet, da inkompatibel mit Ioncube
|
||||
*
|
||||
* @param string|null $cacheFile Absolute path to file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function writeClassMap($cacheFile = null)
|
||||
{
|
||||
if ($cacheFile === null) {
|
||||
$cacheFile = $this->config->getClassMapCacheFile();
|
||||
}
|
||||
|
||||
$classMap = $this->installer->getClassMap();
|
||||
|
||||
$lines = [];
|
||||
$lines[] = '<?php';
|
||||
$lines[] = '';
|
||||
$lines[] = 'return array(';
|
||||
foreach ($classMap as $class => $file) {
|
||||
$lines[] .= sprintf(' %s => %s,', var_export($class, true), var_export($file, true));
|
||||
}
|
||||
$lines[] = ');';
|
||||
|
||||
$contents = '';
|
||||
foreach ($lines as $line) {
|
||||
$contents .= $line . "\n";
|
||||
}
|
||||
|
||||
$this->writeCacheFile($cacheFile, $contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $cacheFile Absolute path to file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function writeServiceCache($cacheFile = null)
|
||||
{
|
||||
if ($cacheFile === null) {
|
||||
$cacheFile = $this->config->getServiceCacheFile();
|
||||
}
|
||||
|
||||
$serviceFactories = $this->installer->getServices();
|
||||
$content = "<?php \n\nreturn array(\n";
|
||||
|
||||
foreach ($serviceFactories as $service => $callable) {
|
||||
$content .= sprintf(
|
||||
" %s => array(%s, %s),\n",
|
||||
var_export($service, true),
|
||||
var_export($callable[0], true),
|
||||
var_export($callable[1], true)
|
||||
);
|
||||
}
|
||||
|
||||
$content .= ");\n";
|
||||
|
||||
$this->writeCacheFile($cacheFile, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $cacheFile Absolute path to file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function writeJavascriptCache($cacheFile = null)
|
||||
{
|
||||
if ($cacheFile === null) {
|
||||
$cacheFile = $this->config->getJavascriptCacheFile();
|
||||
}
|
||||
|
||||
$javascript = $this->installer->getJavascriptFiles();
|
||||
$content = "<?php \n\nreturn " . var_export($javascript, true) . ";\n";
|
||||
|
||||
$this->writeCacheFile($cacheFile, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cacheFile
|
||||
* @param string $contents
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function writeCacheFile($cacheFile, $contents)
|
||||
{
|
||||
if (!@file_put_contents($cacheFile, $contents)) {
|
||||
throw new RuntimeException(sprintf(
|
||||
'Cache-Datei "%s" konnte nicht erzeugt werden. Vermutlich fehlen Schreibrechte in %s',
|
||||
$cacheFile, $this->config->getUserDataTempDir()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\Installer;
|
||||
|
||||
/**
|
||||
* Resolves full-qualified class names (PSR-4) by file path
|
||||
*/
|
||||
final class Psr4ClassNameResolver
|
||||
{
|
||||
/** @var array $prefixes Registered namespace prefixes */
|
||||
private $prefixes = [];
|
||||
|
||||
/** @var array $excludes Excludes files */
|
||||
private $excludes = [];
|
||||
|
||||
/**
|
||||
* @param array $prefixes
|
||||
*/
|
||||
public function __construct(array $prefixes = [])
|
||||
{
|
||||
foreach ($prefixes as $prefix => $fileDir) {
|
||||
$this->addNamespace($prefix, $fileDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @example addNamespace('App\\', '/path/to/src')
|
||||
*
|
||||
* @param string $prefix Namespace prefix, e.g. App\
|
||||
* @param string $baseDir Absolute path to directory
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addNamespace($prefix, $baseDir)
|
||||
{
|
||||
// Normalize inputs
|
||||
$prefix = trim($prefix, '\\') . '\\';
|
||||
$baseDir = rtrim($baseDir, '/') . '/';
|
||||
|
||||
$this->prefixes[$prefix] = $baseDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filePath Absolute path to file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function excludeFile($filePath)
|
||||
{
|
||||
$this->excludes[] = $filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filePath Absolute path to class file
|
||||
*
|
||||
* @return string|null Full-qualified class name
|
||||
*/
|
||||
public function resolveClassName($filePath)
|
||||
{
|
||||
// .src.php are built by the Build-Server and are not needed for execution
|
||||
if (strpos($filePath, '.src.php') !== false) {
|
||||
return null;
|
||||
}
|
||||
if (in_array($filePath, $this->excludes, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->prefixes as $prefix => $baseDir) {
|
||||
if (strpos($filePath, $baseDir) === 0) {
|
||||
$offset = strlen($baseDir);
|
||||
$relativePath = substr($filePath, $offset);
|
||||
$relativePath = str_ireplace('.php', '', $relativePath);
|
||||
$relativeNamespace = str_replace('/', '\\', $relativePath);
|
||||
|
||||
return $prefix . $relativeNamespace;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Xentral\Core\Installer;
|
||||
|
||||
use Xentral\Components\Database\DatabaseConfig;
|
||||
use Xentral\Components\Database\Exception\EscapingException;
|
||||
use Xentral\Components\SchemaCreator\Collection\SchemaCollection;
|
||||
use Xentral\Components\SchemaCreator\Exception\LineGeneratorException;
|
||||
use Xentral\Components\SchemaCreator\Exception\SchemaCreatorTableException;
|
||||
use Xentral\Components\SchemaCreator\SchemaCreator;
|
||||
use RuntimeException;
|
||||
|
||||
final class TableSchemaEnsurer
|
||||
{
|
||||
/** @var SchemaCreator $creator */
|
||||
private $creator;
|
||||
|
||||
/** @var InstallerCacheConfig $config */
|
||||
private $config;
|
||||
|
||||
/** @var DatabaseConfig $dbConfig */
|
||||
private $dbConfig;
|
||||
|
||||
/**
|
||||
* @param SchemaCreator $creator
|
||||
* @param InstallerCacheConfig $config
|
||||
* @param DatabaseConfig $dbConfig
|
||||
*/
|
||||
public function __construct(SchemaCreator $creator, InstallerCacheConfig $config, DatabaseConfig $dbConfig)
|
||||
{
|
||||
$this->creator = $creator;
|
||||
$this->config = $config;
|
||||
$this->dbConfig = $dbConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SchemaCollection $collection
|
||||
*
|
||||
* @throws RuntimeException
|
||||
* @throws EscapingException
|
||||
* @throws LineGeneratorException
|
||||
* @throws SchemaCreatorTableException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ensureSchemas(SchemaCollection $collection): void
|
||||
{
|
||||
$sqlSchema = [];
|
||||
foreach ($collection as $schema) {
|
||||
$schemaIndexes = $schema->getIndexes();
|
||||
if (!$schemaIndexes->hasPrimaryKey()) {
|
||||
throw new RuntimeException(
|
||||
sprintf(
|
||||
'Primary key is missing in schema for table "%s".',
|
||||
$schema->getTable()
|
||||
)
|
||||
);
|
||||
}
|
||||
$query = $this->creator->getSqlSchema($schema);
|
||||
if (!empty($query)) {
|
||||
$query = sprintf('%s;',rtrim($query, ';'));
|
||||
}
|
||||
$sqlSchema[] = $query;
|
||||
}
|
||||
|
||||
$sql = implode("\n", $sqlSchema);
|
||||
|
||||
if (trim($sql) === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$sqlFile = $this->getSqlFilePath();
|
||||
if (!@file_put_contents($sqlFile, $sql)) {
|
||||
throw new RuntimeException(
|
||||
sprintf(
|
||||
'SQL-Datei "%s" cannot be created. Probably there are no write permissions in %s',
|
||||
$sqlFile,
|
||||
$this->config->getUserDataTempDir()
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->importSql($sqlFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sqlFile
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function importSql(string $sqlFile): void
|
||||
{
|
||||
if (!$this->canExec()) {
|
||||
throw new RuntimeException(
|
||||
'PHP function "exec" is not available or has been disabled by php.ini settings.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_file($sqlFile) || filesize($sqlFile) < 1) {
|
||||
return;
|
||||
}
|
||||
@exec(
|
||||
sprintf(
|
||||
'mysql -D%s -h%s -u%s -p%s < %s',
|
||||
escapeshellarg($this->dbConfig->getDatabase()),
|
||||
escapeshellarg($this->dbConfig->getHostname()),
|
||||
escapeshellarg($this->dbConfig->getUsername()),
|
||||
escapeshellarg($this->dbConfig->getPassword()),
|
||||
escapeshellarg($sqlFile)
|
||||
),
|
||||
$output,
|
||||
$returnVar
|
||||
);
|
||||
|
||||
switch ($returnVar) {
|
||||
case 0:
|
||||
// No error
|
||||
break;
|
||||
case 1:
|
||||
throw new RuntimeException('General error: ' . implode(' ', $output));
|
||||
break;
|
||||
case 126:
|
||||
throw new RuntimeException('Can not execute "mysql" command.');
|
||||
break;
|
||||
case 127:
|
||||
throw new RuntimeException('Command "mysql" not found.');
|
||||
break;
|
||||
}
|
||||
|
||||
unlink($sqlFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getSqlFilePath(): string
|
||||
{
|
||||
return $this->config->getUserDataTempDir() . '/' . uniqid('schema-', true) . '.sql';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function canExec(): bool
|
||||
{
|
||||
$functionName = 'exec';
|
||||
if (!function_exists($functionName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$disabledFunctions = explode(',', ini_get('disable_functions'));
|
||||
foreach ($disabledFunctions as $disabledFunction) {
|
||||
if (trim($disabledFunction) === $functionName) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\LegacyConfig;
|
||||
|
||||
use Config;
|
||||
use Xentral\Core\LegacyConfig\Exception\MultiDbConfigNotFoundException;
|
||||
|
||||
final class ConfigLoader
|
||||
{
|
||||
/**
|
||||
* @return Config
|
||||
*/
|
||||
public static function load()
|
||||
{
|
||||
$defaultConfig = self::loadDefaultConfig();
|
||||
|
||||
// Ist MultiDb-Key gesetzt?
|
||||
$dbSelect = self::determineMultiDbConfigKey();
|
||||
if ($dbSelect === null) {
|
||||
return $defaultConfig;
|
||||
}
|
||||
|
||||
// Ist MultiDb-Config vorhanden?
|
||||
$multiDbArray = self::loadMultiDbArray();
|
||||
if (empty($multiDbArray)) {
|
||||
return $defaultConfig;
|
||||
}
|
||||
|
||||
// MultiDb-Array aufbereiten
|
||||
$multiDbArray = MultiDbArrayHydrator::hydrate($defaultConfig, $multiDbArray);
|
||||
|
||||
// Zuerst MultiDB-Keys durchsuchen (wenn assoziatives Array)
|
||||
foreach ($multiDbArray as $multiDbKey => $multiDbItem) {
|
||||
if ($dbSelect === $multiDbKey) {
|
||||
return self::buildConfigFromMultiDbArray($defaultConfig, $multiDbItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: MultiDB-Konfigurationen nach Feld 'dbname' durchsuchen
|
||||
foreach ($multiDbArray as $multiDbKey => $multiDbItem) {
|
||||
if ($dbSelect === $multiDbItem['dbname']) {
|
||||
return self::buildConfigFromMultiDbArray($defaultConfig, $multiDbItem);
|
||||
}
|
||||
}
|
||||
|
||||
throw new MultiDbConfigNotFoundException(sprintf(
|
||||
'MultiDb-Config "%s" not found.', $dbSelect
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Config[]|array
|
||||
*/
|
||||
public static function loadAll()
|
||||
{
|
||||
$defaultConfig = self::loadDefaultConfig();
|
||||
$multiDbArray = self::loadMultiDbArray();
|
||||
|
||||
// MultiDb-Array aufbereiten
|
||||
$multiDbArray = MultiDbArrayHydrator::hydrate($defaultConfig, $multiDbArray);
|
||||
|
||||
$result = [];
|
||||
foreach ($multiDbArray as $multiDbKey => $multiDbItem) {
|
||||
$result[$multiDbKey] = self::buildConfigFromMultiDbArray($defaultConfig, $multiDbItem);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Config[]|array
|
||||
*/
|
||||
public static function loadAllWithActiveCronjobs()
|
||||
{
|
||||
$defaultConfig = self::loadDefaultConfig();
|
||||
$multiDbArray = self::loadMultiDbArray();
|
||||
|
||||
// MultiDb-Array aufbereiten
|
||||
$multiDbArray = MultiDbArrayHydrator::hydrate($defaultConfig, $multiDbArray);
|
||||
|
||||
$result = [];
|
||||
foreach ($multiDbArray as $multiDbKey => $multiDbItem) {
|
||||
if ($multiDbItem['cronjob'] === true) {
|
||||
$result[$multiDbKey] = self::buildConfigFromMultiDbArray($defaultConfig, $multiDbItem);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]|array
|
||||
*/
|
||||
public static function loadAllDescriptions()
|
||||
{
|
||||
$defaultConfig = self::loadDefaultConfig();
|
||||
$multiDbArray = self::loadMultiDbArray();
|
||||
|
||||
// MultiDb-Array aufbereiten
|
||||
$multiDbArray = MultiDbArrayHydrator::hydrate($defaultConfig, $multiDbArray);
|
||||
|
||||
$result = [];
|
||||
foreach ($multiDbArray as $multiDbKey => $multiDbItem) {
|
||||
$result[$multiDbKey] = $multiDbItem['description'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Config $defaultConfig
|
||||
* @param array $multiDbItem
|
||||
*
|
||||
* @return Config
|
||||
*/
|
||||
private static function buildConfigFromMultiDbArray($defaultConfig, $multiDbItem)
|
||||
{
|
||||
$config = clone $defaultConfig;
|
||||
|
||||
$config->WFdbhost = $multiDbItem['dbhost'];
|
||||
$config->WFdbport = $multiDbItem['dbport'];
|
||||
$config->WFdbname = $multiDbItem['dbname'];
|
||||
$config->WFdbuser = $multiDbItem['dbuser'];
|
||||
$config->WFdbpass = $multiDbItem['dbpass'];
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Config
|
||||
*/
|
||||
private static function loadDefaultConfig()
|
||||
{
|
||||
if (!class_exists('\\Config', true)) {
|
||||
$configClassFilePath = dirname(dirname(dirname(__DIR__))) . '/conf/main.conf.php';
|
||||
if (is_file($configClassFilePath)) {
|
||||
require_once $configClassFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
return new Config();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private static function loadMultiDbArray()
|
||||
{
|
||||
$multiDbArray = [];
|
||||
|
||||
$multiDbFilePath = dirname(dirname(dirname(__DIR__))) . '/conf/multidb.conf.php';
|
||||
if (is_file($multiDbFilePath)) {
|
||||
$multiDbArray = include $multiDbFilePath;
|
||||
if (!is_array($multiDbArray)) {
|
||||
$multiDbArray = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $multiDbArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @uses $_POST
|
||||
* @uses $_COOKIE
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private static function determineMultiDbConfigKey()
|
||||
{
|
||||
/**
|
||||
* Wenn MULTIDB-Konstante gesetzt, dann kommt der Request aus der Rest-API.
|
||||
*
|
||||
* @see www/api/bootstrap.php
|
||||
*/
|
||||
if (defined('MULTIDB')) {
|
||||
return constant('MULTIDB');
|
||||
}
|
||||
|
||||
/**
|
||||
* Wenn POST['db'] und POST['dbselect'] gesetzt, dann kommt der Request übers vom Login-Formular (Frontend).
|
||||
*
|
||||
* @see \Acl::Login()
|
||||
*/
|
||||
if (isset($_POST['db'], $_POST['dbselect']) && $_POST['dbselect'] === 'true') {
|
||||
return $_POST['db'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Wenn COOKIE['DBSELECTED'] gesetzt, dann kommt der Request übers Frontend und
|
||||
* der Login war in einem frührem Request erfolgreich.
|
||||
*
|
||||
* @see \Acl::Login()
|
||||
*/
|
||||
if (isset($_COOKIE['DBSELECTED']) && !empty($_COOKIE['DBSELECTED'])) {
|
||||
return $_COOKIE['DBSELECTED'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\LegacyConfig\Exception;
|
||||
|
||||
use InvalidArgumentException as SplInvalidArgumentException;
|
||||
|
||||
class InvalidArgumentException extends SplInvalidArgumentException implements LegacyConfigExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\LegacyConfig\Exception;
|
||||
|
||||
use Xentral\Core\Exception\CoreExceptionInterface;
|
||||
|
||||
interface LegacyConfigExceptionInterface extends CoreExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\LegacyConfig\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class MultiDbConfigNotFoundException extends RuntimeException implements LegacyConfigExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Core\LegacyConfig;
|
||||
|
||||
use Config;
|
||||
use Xentral\Core\LegacyConfig\Exception\InvalidArgumentException;
|
||||
|
||||
final class MultiDbArrayHydrator
|
||||
{
|
||||
/**
|
||||
* @param Config $defaultConfig
|
||||
* @param array $multiDbArray
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return array $multiDbArray
|
||||
*/
|
||||
public static function hydrate(Config $defaultConfig, $multiDbArray)
|
||||
{
|
||||
if (!is_array($multiDbArray)) {
|
||||
throw new InvalidArgumentException('Can not hydrate array. Parameter is not an array.');
|
||||
}
|
||||
|
||||
$result = $multiDbArray;
|
||||
$result = self::fillEmptyValues($defaultConfig, $result);
|
||||
$result = self::includeDefaultConfig($defaultConfig, $result);
|
||||
$result = self::prepareArrayKeys($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fülle leere Werte mit Werten aus der Default-Config, so dass alle Einträge die gleiche Struktur haben.
|
||||
*
|
||||
* @param Config $defaultConfig
|
||||
* @param array $multiDbArray
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function fillEmptyValues(Config $defaultConfig, $multiDbArray)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
// MultiDb-Array mit Werten aus der Default-Config füllen
|
||||
foreach ($multiDbArray as $key => $item) {
|
||||
|
||||
// Beschreibung darf nicht leer sein; als Fallback den Datenbanknamen verwenden
|
||||
$description = !empty($item['description']) ? $item['description'] : $defaultConfig->WFdbname;
|
||||
|
||||
// Cronjobs nur aktivieren, wenn Einstellung vorhanden und gesetzt (Default `false`).
|
||||
$cronjobsActive = (int)$item['cronjob'] === 1;
|
||||
|
||||
if(!empty($item['dbname']) && $defaultConfig->WFdbname === $item['dbname']) {
|
||||
$item = [];
|
||||
}
|
||||
|
||||
$dbhost = !empty($item['dbhost']) ? $item['dbhost'] : $defaultConfig->WFdbhost;
|
||||
$dbport = !empty($item['dbport']) ? $item['dbport'] : $defaultConfig->WFdbport;
|
||||
$dbname = !empty($item['dbname']) ? $item['dbname'] : $defaultConfig->WFdbname;
|
||||
$dbuser = !empty($item['dbuser']) ? $item['dbuser'] : $defaultConfig->WFdbuser;
|
||||
$dbpass = !empty($item['dbpass']) ? $item['dbpass'] : $defaultConfig->WFdbpass;
|
||||
|
||||
$result[$key] = [
|
||||
'description' => $description,
|
||||
'dbhost' => $dbhost,
|
||||
'dbport' => $dbport,
|
||||
'dbname' => $dbname,
|
||||
'dbuser' => $dbuser,
|
||||
'dbpass' => $dbpass,
|
||||
'cronjob' => $cronjobsActive,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stellt sicher dass die Default-Config im MultiDbArray vorkommt
|
||||
*
|
||||
* @param Config $defaultConfig
|
||||
* @param array $multiDbArray
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function includeDefaultConfig(Config $defaultConfig, $multiDbArray)
|
||||
{
|
||||
// Prüfen ob Default-Config in MultiDb-Array vorhanden ist
|
||||
foreach ($multiDbArray as $key => $item) {
|
||||
if ($item['dbhost'] === $defaultConfig->WFdbhost &&
|
||||
$item['dbport'] === $defaultConfig->WFdbport &&
|
||||
$item['dbname'] === $defaultConfig->WFdbname) {
|
||||
return $multiDbArray; // Default-Config ist bereits enthalten
|
||||
}
|
||||
}
|
||||
|
||||
// Default-Config in MultiDb-Array anhängen
|
||||
$defaultDbName = $defaultConfig->WFdbname;
|
||||
$defaultConfigKey = !isset($multiDbArray[$defaultDbName]) ? $defaultDbName : '__default__';
|
||||
$multiDbArray[$defaultConfigKey] = [
|
||||
'description' => $defaultConfig->WFdbname,
|
||||
'dbhost' => $defaultConfig->WFdbhost,
|
||||
'dbport' => $defaultConfig->WFdbport,
|
||||
'dbname' => $defaultConfig->WFdbname,
|
||||
'dbuser' => $defaultConfig->WFdbuser,
|
||||
'dbpass' => $defaultConfig->WFdbpass,
|
||||
'cronjob' => true,
|
||||
];
|
||||
|
||||
return $multiDbArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ersetzt numerische Array-Schlüssel durch den Datenbanknamen
|
||||
*
|
||||
* @param array $multiDbArray
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function prepareArrayKeys($multiDbArray)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($multiDbArray as $key => $item) {
|
||||
if (is_numeric($key)) {
|
||||
$dbname = $item['dbname'];
|
||||
if (!isset($multiDbArray[$dbname])) {
|
||||
$key = $dbname;
|
||||
}
|
||||
}
|
||||
$result[$key] = $item;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user