From f440a450403edcdc88010a775fdb83598adb5b78 Mon Sep 17 00:00:00 2001
From: hail-cookies <81558994+hail-cookies@users.noreply.github.com>
Date: Tue, 25 Nov 2025 17:34:36 +0100
Subject: [PATCH 1/9] NEW AbstractServerInstaller
---
.../AppInstallers/AbstractServerInstaller.php | 112 ++++++++++++++++++
.../AppInstallers/ApacheServerInstaller.php | 109 +++++++++++++++++
.../AppInstallers/IISServerInstaller.php | 108 ++++++-----------
CoreApp.php | 88 ++++----------
4 files changed, 274 insertions(+), 143 deletions(-)
create mode 100644 CommonLogic/AppInstallers/AbstractServerInstaller.php
create mode 100644 CommonLogic/AppInstallers/ApacheServerInstaller.php
diff --git a/CommonLogic/AppInstallers/AbstractServerInstaller.php b/CommonLogic/AppInstallers/AbstractServerInstaller.php
new file mode 100644
index 000000000..602ec5580
--- /dev/null
+++ b/CommonLogic/AppInstallers/AbstractServerInstaller.php
@@ -0,0 +1,112 @@
+getSelectorInstalling()))
+ ->setFilePath(Filemanager::pathJoin([
+ $this->getWorkbench()->getInstallationPath(),
+ $this->getConfigFileName()
+ ]))
+ ->setFileTemplatePath($this->getConfigTemplatePathRelative())
+ ->setMarkerBegin("\n{$this->stringToComment('BEGIN [#marker#]')}")
+ ->setMarkerEnd($this->stringToComment('END [#marker#]'));
+
+ $this->configInstaller = $configInstaller;
+ }
+
+ /**
+ * Returns the filename for the config file created by this installer.
+ * For apache this would be `.htaccess` and for IIS it would be `Web.config`.
+ *
+ * @return string
+ */
+ protected abstract function getConfigFileName() : string;
+
+ /**
+ * Returns the filepath for the config template relative to the CoreApp folder.
+ * This would usually be just the name of the template, e.g. `default.Web.config`.
+ *
+ * @return string
+ */
+ protected abstract function getConfigTemplatePathRelative() : string;
+
+ /**
+ * Turns a string into a config comment.
+ *
+ * @param string $markerText
+ * @return string
+ */
+ protected abstract function stringToComment(string $markerText) : string;
+
+ /**
+ *
+ * {@inheritDoc}
+ * @see \exface\Core\Interfaces\InstallerInterface::backup()
+ */
+ public function backup(string $absolute_path) : \Iterator
+ {
+ yield from $this->configInstaller->backup($absolute_path);
+ }
+
+ /**
+ *
+ * {@inheritDoc}
+ * @see \exface\Core\Interfaces\InstallerInterface::uninstall()
+ */
+ public function uninstall() : \Iterator
+ {
+ yield from $this->configInstaller->uninstall();
+ }
+
+ /**
+ *
+ * {@inheritDoc}
+ * @see \exface\Core\Interfaces\InstallerInterface::install()
+ */
+ public function install(string $source_absolute_path): \Iterator
+ {
+ $indentOuter = $this->getOutputIndentation();
+ $indent = $indentOuter . $indentOuter;
+ $serverType = ServerSoftwareDataType::getServerSoftwareFamily() ?? 'UNKNOWN SERVER SOFTWARE';
+ $serverVersion = ServerSoftwareDataType::getServerSoftwareVersion() ?? 'UNKNOWN VERSION';
+
+ yield $indentOuter . "Server configuration for {$serverType} {$serverVersion}:" . PHP_EOL;
+
+ $this->configInstaller->setOutputIndentation($indent);
+ yield $indent . "Using \"{$this->getConfigTemplatePathRelative()}\" template for {$serverType}." . PHP_EOL;
+ yield from $this->configInstaller->install($source_absolute_path);
+ $this->configInstaller->setOutputIndentation($indentOuter);
+ }
+
+ /**
+ *
+ * {@inheritDoc}
+ * @see \exface\Core\CommonLogic\AppInstallers\AbstractAppInstaller::setOutputIndentation()
+ */
+ public function setOutputIndentation(string $value) : AbstractAppInstaller
+ {
+ $this->configInstaller->setOutputIndentation($value);
+ return parent::setOutputIndentation($value);
+ }
+}
\ No newline at end of file
diff --git a/CommonLogic/AppInstallers/ApacheServerInstaller.php b/CommonLogic/AppInstallers/ApacheServerInstaller.php
new file mode 100644
index 000000000..4d5af17b0
--- /dev/null
+++ b/CommonLogic/AppInstallers/ApacheServerInstaller.php
@@ -0,0 +1,109 @@
+configInstaller
+ ->addContent('Core URLs', $this->getContentCoreUrls())
+ ->addContent('Core Security', $this->getContentSecurityRules())
+ ->addContent("zlib compression OFF for WebConsoleFacade", "
+
+ php_flag zlib.output_compression Off
+
+
+");;
+ }
+
+ /**
+ * Returns a string containing config options for core URLs.
+ *
+ * Edit the return value if you want to change the core URL rules.
+ *
+ * @return string
+ */
+ protected function getContentCoreUrls() : string
+ {
+ return "
+
+# API requests
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ^api/.*$ vendor/exface/core/index.php [L,QSA,NC]
+
+# Force trailing slash on requests to the root folder of the workbench
+# E.g. me.com/exface -> me.com/exface/
+RewriteCond %{REQUEST_URI} ^$
+RewriteRule ^$ %{REQUEST_URI} [R=301]
+
+# index request without any path
+RewriteRule ^/?$ vendor/exface/core/index.php [L,QSA]
+
+# Requests to UI pages
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ^[^/]*$ vendor/exface/core/index.php [L,QSA]
+
+";
+ }
+
+ /**
+ * Returns a string containing config options for security rules.
+ *
+ * Edit the return value if you want to change the core security rules.
+ *
+ * @return string
+ */
+ protected function getContentSecurityRules() : string
+ {
+ return "
+
+# Block direct access to PHP scripts
+RewriteCond %{REQUEST_FILENAME} -f
+RewriteCond %{REQUEST_FILENAME} !vendor/exface/core/index.php [NC]
+RewriteRule ^vendor/.*\.php$ - [F,L,NC]
+
+# Block requests to config, cache, backup, etc.
+RewriteRule ^(config|backup|translations|logs)/.*$ - [F,NC]
+# Block requests to system files (starting with a dot) in the data folder
+RewriteRule ^data/\..*$ - [F,NC]
+
+# Block .html files.
+RewriteRule ^vendor/.*\.html$ - [F,L,NC]
+
+# Block library docs.
+RewriteRule ^vendor/.*/gh-pages.*$ - [F,L,NC]
+
+";
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getConfigFileName(): string
+ {
+ return '.htaccess';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getConfigTemplatePathRelative(): string
+ {
+ return 'default.htaccess';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function stringToComment(string $markerText): string
+ {
+ return "# {$markerText}";
+ }
+}
\ No newline at end of file
diff --git a/CommonLogic/AppInstallers/IISServerInstaller.php b/CommonLogic/AppInstallers/IISServerInstaller.php
index 416af5f78..edba6f74f 100644
--- a/CommonLogic/AppInstallers/IISServerInstaller.php
+++ b/CommonLogic/AppInstallers/IISServerInstaller.php
@@ -2,90 +2,31 @@
namespace exface\Core\CommonLogic\AppInstallers;
use exface\Core\DataTypes\StringDataType;
-use exface\Core\Interfaces\Selectors\SelectorInterface;
-use exface\Core\CommonLogic\Filemanager;
use exface\Core\DataTypes\ServerSoftwareDataType;
/**
- * This installer takes care of file permissions, web.config an other settings required to run on Microsoft IIS.
+ * This installer takes care of file permissions, web.config and other settings required to run on Microsoft IIS.
*
* @author Ralf Mulansky
*
*/
-class IISServerInstaller extends AbstractAppInstaller
+class IISServerInstaller extends AbstractServerInstaller
{
- private $webConfigInstaller = null;
-
- private $webConfigVersion = null;
-
- /**
- *
- * @param SelectorInterface $selectorToInstall
- */
- public function __construct(SelectorInterface $selectorToInstall)
- {
- parent::__construct($selectorToInstall);
-
- // Web.config for IIS servers
- $iisVersion = ServerSoftwareDataType::getServerSoftwareVersion();
- switch (true) {
- case $iisVersion < '10.0':
- $tpl = 'default8.Web.config';
- $this->webConfigVersion = '8.5+';
- break;
- default:
- $tpl = 'default.Web.config';
- $this->webConfigVersion = '10+';
- break;
- }
- $webconfigInstaller = new FileContentInstaller($this->getSelectorInstalling());
- $webconfigInstaller
- ->setFilePath(Filemanager::pathJoin([$this->getWorkbench()->getInstallationPath(), 'Web.config']))
- ->setFileTemplatePath($tpl)
- ->setMarkerBegin("\n")
- ->setMarkerEnd("");;
- $this->webConfigInstaller = $webconfigInstaller;
- }
-
- /**
- *
- * {@inheritDoc}
- * @see \exface\Core\Interfaces\InstallerInterface::backup()
- */
- public function backup(string $absolute_path) : \Iterator
- {
- yield from $this->webConfigInstaller->backup($absolute_path);
- }
-
- /**
- *
- * {@inheritDoc}
- * @see \exface\Core\Interfaces\InstallerInterface::uninstall()
- */
- public function uninstall() : \Iterator
- {
- yield from $this->webConfigInstaller->uninstall();
- }
-
/**
*
* {@inheritDoc}
* @see \exface\Core\Interfaces\InstallerInterface::install()
*/
- public function install(string $source_absolute_path): \Iterator
+ public function install(string $source_absolute_path): \Iterator
{
- $indentOuter = $this->getOutputIndentation();
- $indent = $indentOuter . $indentOuter;
-
- yield $indentOuter . "Server configuration for Microsoft IIS " . ServerSoftwareDataType::getServerSoftwareVersion() ?? 'UNKNOWN VERSION' . PHP_EOL;
-
- $this->webConfigInstaller->setOutputIndentation($indent);
- yield $indent . 'Using web.config template for IIS ' . $this->webConfigVersion . PHP_EOL;
- yield from $this->webConfigInstaller->install($source_absolute_path);
- $this->webConfigInstaller->setOutputIndentation($indentOuter);
-
+ yield parent::install($source_absolute_path);
+
$fm = $this->getWorkbench()->filemanager();
$user = 'IUSR';
+
+ $indentOuter = $this->getOutputIndentation();
+ $indent = $indentOuter . $indentOuter;
+
yield $indent . $this->setPermissionsForPath($fm->getPathToDataFolder(), $user) . PHP_EOL;
yield $indent . $this->setPermissionsForPath($fm->getPathToLogFolder(), $user) . PHP_EOL;
yield $indent . $this->setPermissionsForPath($fm->getPathToConfigFolder(), $user) . PHP_EOL;
@@ -113,15 +54,32 @@ protected function setPermissionsForPath(string $path, string $user): string
}
return "Permission for the user '{$user}' and folder/file '{$shortPath}' changed!";
}
-
+
+ protected function getConfigFileName(): string
+ {
+ return 'Web.config';
+ }
+
/**
- *
- * {@inheritDoc}
- * @see \exface\Core\CommonLogic\AppInstallers\AbstractAppInstaller::setOutputIndentation()
+ * @inheritDoc
+ */
+ protected function getConfigTemplatePathRelative(): string
+ {
+ // Web.config for IIS servers
+ $iisVersion = ServerSoftwareDataType::getServerSoftwareVersion();
+ return match (true) {
+ $iisVersion < '10.0' =>
+ 'default8.Web.config',
+ default =>
+ 'default.Web.config',
+ };
+ }
+
+ /**
+ * @inheritDoc
*/
- public function setOutputIndentation(string $value) : AbstractAppInstaller
+ protected function stringToComment(string $markerText): string
{
- $this->webConfigInstaller->setOutputIndentation($value);
- return parent::setOutputIndentation($value);
+ return "";
}
}
\ No newline at end of file
diff --git a/CoreApp.php b/CoreApp.php
index a377d00ab..fecf683d1 100644
--- a/CoreApp.php
+++ b/CoreApp.php
@@ -1,6 +1,7 @@
addInstaller(new CoreInstaller($this->getSelector()), true);
-
- // .htaccess for Apache servers
-
- $htaccessInstaller = new FileContentInstaller($this->getSelector());
- $htaccessInstaller
- ->setFilePath(Filemanager::pathJoin([$this->getWorkbench()->getInstallationPath(), '.htaccess']))
- ->setFileTemplatePath('default.htaccess')
- ->setMarkerBegin("\n# BEGIN [#marker#]")
- ->setMarkerEnd('# END [#marker#]')
- ->addContent('Core URLs', "
-
-# API requests
-RewriteCond %{REQUEST_FILENAME} !-f
-RewriteCond %{REQUEST_FILENAME} !-d
-RewriteRule ^api/.*$ vendor/exface/core/index.php [L,QSA,NC]
-
-# Force trailing slash on requests to the root folder of the workbench
-# E.g. me.com/exface -> me.com/exface/
-RewriteCond %{REQUEST_URI} ^$
-RewriteRule ^$ %{REQUEST_URI} [R=301]
-
-# index request without any path
-RewriteRule ^/?$ vendor/exface/core/index.php [L,QSA]
-
-# Requests to UI pages
-RewriteCond %{REQUEST_FILENAME} !-f
-RewriteCond %{REQUEST_FILENAME} !-d
-RewriteRule ^[^/]*$ vendor/exface/core/index.php [L,QSA]
-
-")
- ->addContent('Core Security', "
-# Block direct access to PHP scripts
-RewriteCond %{REQUEST_FILENAME} -f
-RewriteCond %{REQUEST_FILENAME} !vendor/exface/core/index.php [NC]
-RewriteRule ^vendor/.*\.php$ - [F,L,NC]
-
-# Block requests to config, cache, backup, etc.
-RewriteRule ^(config|backup|translations|logs)/.*$ - [F,NC]
-# Block requests to system files (starting with a dot) in the data folder
-RewriteRule ^data/\..*$ - [F,NC]
-
-# Block .html files.
-RewriteRule ^vendor/.*\.html$ - [F,L,NC]
-
-# Block library docs.
-RewriteRule ^vendor/.*/gh-pages.*$ - [F,L,NC]
-
-");
- $installer->addInstaller($htaccessInstaller);
-
// robot.txt
-
$robotsTxtInstaller = new FileContentInstaller($this->getSelector());
$robotsTxtInstaller
->setFilePath(Filemanager::pathJoin([$this->getWorkbench()->getInstallationPath(), 'robots.txt']))
@@ -136,12 +86,6 @@ public function getInstaller(InstallerInterface $injected_installer = null)
$tplInstaller = new HttpFacadeInstaller($this->getSelector());
$tplInstaller->setFacade(FacadeFactory::createFromString(WebConsoleFacade::class, $this->getWorkbench()));
$installer->addInstaller($tplInstaller);
- $htaccessInstaller->addContent("zlib compression off for webconsole facade\n", "
-
- php_flag zlib.output_compression Off
-
-
-");
// HttpTask facade
$tplInstaller = new HttpFacadeInstaller($this->getSelector());
@@ -159,17 +103,8 @@ public function getInstaller(InstallerInterface $injected_installer = null)
$installer->addInstaller($tplInstaller);
// Server installer (e.g. for Microsoft IIS)
- $serverInstallerClass = $this->getWorkbench()->getConfig()->getOption("INSTALLER.SERVER_INSTALLER.CLASS");
- // Autodetect server installer if not set explicitly
- if ($serverInstallerClass === null) {
- switch (true) {
- case ServerSoftwareDataType::isServerIIS():
- $serverInstallerClass = '\\' . ltrim(IISServerInstaller::class, "\\");
- break;
- // TODO add installers for apache and nginx here!
- }
- }
- if ($serverInstallerClass != null) {
+ $serverInstallerClass = $this->getServerInstallerClass();
+ if ($serverInstallerClass !== null) {
$serverInstaller = new $serverInstallerClass($this->getSelector());
$installer->addInstaller($serverInstaller);
}
@@ -186,6 +121,23 @@ public function getInstaller(InstallerInterface $injected_installer = null)
return $installer;
}
+
+ /**
+ * @return string|null
+ */
+ protected function getServerInstallerClass() : string|null
+ {
+ $serverInstallerClass = $this->getWorkbench()->getConfig()->getOption("INSTALLER.SERVER_INSTALLER.CLASS");
+
+ // Autodetect server installer if not set explicitly
+ return match (true) {
+ !empty($serverInstallerClass) => $serverInstallerClass,
+ ServerSoftwareDataType::isServerIIS() => '\\' . ltrim(IISServerInstaller::class, "\\"),
+ ServerSoftwareDataType::isServerNginx() => null,
+ ServerSoftwareDataType::isServerApache() => '\\' . ltrim(ApacheServerInstaller::class, "\\"),
+ default => null,
+ };
+ }
/**
* The configuration of the core consists of two parts: system config and cora app config.
From 8700af19aaeb8b510de0cc745630ffcf39722a36 Mon Sep 17 00:00:00 2001
From: hail-cookies <81558994+hail-cookies@users.noreply.github.com>
Date: Wed, 26 Nov 2025 15:04:02 +0100
Subject: [PATCH 2/9] DEV FileContentsInstaller improved formatting and new
configuration options
---
.../AppInstallers/AbstractServerInstaller.php | 9 +-
.../AppInstallers/ApacheServerInstaller.php | 29 ++----
.../AppInstallers/FileContentInstaller.php | 93 +++++++++++++++++--
.../AppInstallers/IISServerInstaller.php | 6 +-
4 files changed, 102 insertions(+), 35 deletions(-)
diff --git a/CommonLogic/AppInstallers/AbstractServerInstaller.php b/CommonLogic/AppInstallers/AbstractServerInstaller.php
index 602ec5580..86fcd84df 100644
--- a/CommonLogic/AppInstallers/AbstractServerInstaller.php
+++ b/CommonLogic/AppInstallers/AbstractServerInstaller.php
@@ -1,7 +1,6 @@
getConfigFileName()
]))
->setFileTemplatePath($this->getConfigTemplatePathRelative())
- ->setMarkerBegin("\n{$this->stringToComment('BEGIN [#marker#]')}")
+ ->setMarkerBegin($this->stringToComment('BEGIN [#marker#]'))
->setMarkerEnd($this->stringToComment('END [#marker#]'));
$this->configInstaller = $configInstaller;
@@ -54,10 +53,10 @@ protected abstract function getConfigTemplatePathRelative() : string;
/**
* Turns a string into a config comment.
*
- * @param string $markerText
+ * @param string $comment
* @return string
*/
- protected abstract function stringToComment(string $markerText) : string;
+ protected abstract function stringToComment(string $comment) : string;
/**
*
diff --git a/CommonLogic/AppInstallers/ApacheServerInstaller.php b/CommonLogic/AppInstallers/ApacheServerInstaller.php
index 4d5af17b0..48cd4f3a1 100644
--- a/CommonLogic/AppInstallers/ApacheServerInstaller.php
+++ b/CommonLogic/AppInstallers/ApacheServerInstaller.php
@@ -13,14 +13,13 @@ public function __construct(SelectorInterface $selectorToInstall)
$this->configInstaller
->addContent('Core URLs', $this->getContentCoreUrls())
->addContent('Core Security', $this->getContentSecurityRules())
- ->addContent("zlib compression OFF for WebConsoleFacade", "
-
+ ->addContent("zlib compression OFF for WebConsoleFacade",
+"
php_flag zlib.output_compression Off
-
-
-");;
+");
}
+
/**
* Returns a string containing config options for core URLs.
*
@@ -30,9 +29,7 @@ public function __construct(SelectorInterface $selectorToInstall)
*/
protected function getContentCoreUrls() : string
{
- return "
-
-# API requests
+ return "# API requests
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^api/.*$ vendor/exface/core/index.php [L,QSA,NC]
@@ -48,9 +45,7 @@ protected function getContentCoreUrls() : string
# Requests to UI pages
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
-RewriteRule ^[^/]*$ vendor/exface/core/index.php [L,QSA]
-
-";
+RewriteRule ^[^/]*$ vendor/exface/core/index.php [L,QSA]";
}
/**
@@ -62,9 +57,7 @@ protected function getContentCoreUrls() : string
*/
protected function getContentSecurityRules() : string
{
- return "
-
-# Block direct access to PHP scripts
+ return "# Block direct access to PHP scripts
RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{REQUEST_FILENAME} !vendor/exface/core/index.php [NC]
RewriteRule ^vendor/.*\.php$ - [F,L,NC]
@@ -78,9 +71,7 @@ protected function getContentSecurityRules() : string
RewriteRule ^vendor/.*\.html$ - [F,L,NC]
# Block library docs.
-RewriteRule ^vendor/.*/gh-pages.*$ - [F,L,NC]
-
-";
+RewriteRule ^vendor/.*/gh-pages.*$ - [F,L,NC]";
}
/**
@@ -102,8 +93,8 @@ protected function getConfigTemplatePathRelative(): string
/**
* @inheritDoc
*/
- protected function stringToComment(string $markerText): string
+ protected function stringToComment(string $comment): string
{
- return "# {$markerText}";
+ return "# {$comment}";
}
}
\ No newline at end of file
diff --git a/CommonLogic/AppInstallers/FileContentInstaller.php b/CommonLogic/AppInstallers/FileContentInstaller.php
index f1550c39b..722059942 100644
--- a/CommonLogic/AppInstallers/FileContentInstaller.php
+++ b/CommonLogic/AppInstallers/FileContentInstaller.php
@@ -3,6 +3,7 @@
use exface\Core\Exceptions\Installers\InstallerRuntimeError;
use exface\Core\DataTypes\StringDataType;
+use exface\Core\Exceptions\InvalidArgumentException;
/**
*
@@ -11,6 +12,31 @@
*/
class FileContentInstaller extends AbstractAppInstaller
{
+ /**
+ * Behavior setting for missing markers. If applied, missed markers and their contents
+ * will be appended to the end of the file.
+ * @see FileContentInstaller::setMissingMarkerBehavior()
+ */
+ public const MISSING_MARKER_BEHAVIOR_APPEND = 'append';
+
+ /**
+ * Behavior setting for missing markers. If applied, missed markers will be ignored.
+ * @see FileContentInstaller::setMissingMarkerBehavior()
+ */
+ public const MISSING_MARKER_BEHAVIOR_IGNORE = 'ignore';
+
+ /**
+ * Behavior setting for missing markers. If applied, missed markers will cause an error.
+ * @see FileContentInstaller::setMissingMarkerBehavior()
+ */
+ public const MISSING_MARKER_BEHAVIOR_ERROR = 'error';
+
+ private array $missingMarkerBehaviorOptions = [
+ self::MISSING_MARKER_BEHAVIOR_APPEND,
+ self::MISSING_MARKER_BEHAVIOR_IGNORE,
+ self::MISSING_MARKER_BEHAVIOR_ERROR
+ ];
+
private $filePathAbsolute = null;
private $fileTemplatePathAbsolute = null;
@@ -19,6 +45,8 @@ class FileContentInstaller extends AbstractAppInstaller
private $markerEnd = null;
+ private $missingMarkerBehavior = self::MISSING_MARKER_BEHAVIOR_APPEND;
+
/**
* [ marker => content ]
* @var array
@@ -89,12 +117,22 @@ public function install(string $source_absolute_path) : \Iterator
$changesCnt = 0;
foreach ($this->contentArray as $marker => $content) {
$originalContent = $fileContent;
- $begin = StringDataType::replacePlaceholders($this->getMarkerBegin(), ['marker' => preg_quote($marker)]);
- $end = StringDataType::replacePlaceholders($this->getMarkerEnd(), ['marker' => preg_quote($marker)]);
- $pattern = '/' . $begin . '.*' . $end . '/is';
-
+ $begin = trim(StringDataType::replacePlaceholders($this->getMarkerBegin(), ['marker' => preg_quote($marker)]));
+ $end = trim(StringDataType::replacePlaceholders($this->getMarkerEnd(), ['marker' => preg_quote($marker)]));
+ $pattern = '/' . "\s+" . $begin . '.*' . $end . '/is';
+ $newContent = "\n\n" . $begin . "\n" . $content . "\n" . $end;
+
if (preg_match($pattern, $fileContent)) {
- $fileContent = preg_replace($pattern, $begin . $content . $end, $fileContent);
+ if(preg_match('/^(\t+)(?=' . $begin . ')/m', $fileContent, $indents) === 1) {
+ $newContent = StringDataType::indent($newContent, $indents[0]);
+ }
+
+ $fileContent = preg_replace(
+ $pattern,
+ $newContent,
+ $fileContent
+ );
+
if ($fileContent === null) {
throw new InstallerRuntimeError($this, 'Error replacing marker "' . $marker . '" in file "' . $this->getFilePathAbsolute() . '"!');
}
@@ -102,12 +140,23 @@ public function install(string $source_absolute_path) : \Iterator
$changesCnt++;
}
} else {
- $fileContent .= $begin . $content . $end;
- $changesCnt++;
+ switch ($this->getMissingMarkerBehavior()) {
+ // Append.
+ case self::MISSING_MARKER_BEHAVIOR_APPEND:
+ $fileContent .= $newContent . "\n";
+ $changesCnt++;
+ break;
+ // Ignore.
+ case self::MISSING_MARKER_BEHAVIOR_IGNORE:
+ break;
+ // Error.
+ default:
+ throw new InstallerRuntimeError($this, "Failed to find marker \"{$marker}\" in file \"{$this->getFilePathAbsolute()}\".");
+ }
}
}
+
file_put_contents($this->getFilePathAbsolute(), $fileContent);
-
yield ' made ' . $changesCnt . ' changes.' . PHP_EOL;
}
@@ -220,4 +269,32 @@ public function setMarkerEnd(string $marker) : FileContentInstaller
return $this;
}
+ /**
+ * Returns the behavior setting for missing markers.
+ * Check the corresponding `MISSING_MARKER_BEHAVIOR` constants for more info.
+ *
+ * @return string
+ */
+ public function getMissingMarkerBehavior() : string
+ {
+ return $this->missingMarkerBehavior;
+ }
+
+ /**
+ * Set the behavior for missing markers.
+ * Check the corresponding `MISSING_MARKER_BEHAVIOR` constants for more info.
+ *
+ * @param string $value
+ * @return FileContentInstaller
+ */
+ public function setMissingMarkerBehavior(string $value) : FileContentInstaller
+ {
+ if(!in_array($value, $this->missingMarkerBehaviorOptions)) {
+ $allowed = json_encode($this->missingMarkerBehaviorOptions);
+ throw new InvalidArgumentException('Invalid argument "' . $value . '", expected ' . $allowed . '.');
+ }
+
+ $this->missingMarkerBehavior = $value;
+ return $this;
+ }
}
\ No newline at end of file
diff --git a/CommonLogic/AppInstallers/IISServerInstaller.php b/CommonLogic/AppInstallers/IISServerInstaller.php
index edba6f74f..2ae2c1ba9 100644
--- a/CommonLogic/AppInstallers/IISServerInstaller.php
+++ b/CommonLogic/AppInstallers/IISServerInstaller.php
@@ -19,7 +19,7 @@ class IISServerInstaller extends AbstractServerInstaller
*/
public function install(string $source_absolute_path): \Iterator
{
- yield parent::install($source_absolute_path);
+ parent::install($source_absolute_path);
$fm = $this->getWorkbench()->filemanager();
$user = 'IUSR';
@@ -78,8 +78,8 @@ protected function getConfigTemplatePathRelative(): string
/**
* @inheritDoc
*/
- protected function stringToComment(string $markerText): string
+ protected function stringToComment(string $comment): string
{
- return "";
+ return "";
}
}
\ No newline at end of file
From 358aae87149da95c9abd599dc0289f874f57cd95 Mon Sep 17 00:00:00 2001
From: hail-cookies <81558994+hail-cookies@users.noreply.github.com>
Date: Wed, 26 Nov 2025 15:04:37 +0100
Subject: [PATCH 3/9] NEW NgninxServerInstaller and default.nginx.conf
---
.../AppInstallers/NginxServerInstaller.php | 114 ++++++++++++++++++
Install/default.nginx.conf | 23 ++++
2 files changed, 137 insertions(+)
create mode 100644 CommonLogic/AppInstallers/NginxServerInstaller.php
create mode 100644 Install/default.nginx.conf
diff --git a/CommonLogic/AppInstallers/NginxServerInstaller.php b/CommonLogic/AppInstallers/NginxServerInstaller.php
new file mode 100644
index 000000000..c23da9d81
--- /dev/null
+++ b/CommonLogic/AppInstallers/NginxServerInstaller.php
@@ -0,0 +1,114 @@
+configInstaller
+ ->setMissingMarkerBehavior($this->configInstaller::MISSING_MARKER_BEHAVIOR_ERROR)
+ ->addContent('Locations', $this->getLocationsContent());
+ }
+
+ protected function getLocationsContent() : string
+ {
+ return 'location / {
+ if (!-e $request_filename){
+ rewrite ^/api/.*$ /vendor/exface/Core/index.php;
+ }
+
+ if ($request_uri ~ "^$"){
+ rewrite ^/$ /$request_uri redirect;
+ }
+
+ rewrite ^/?$ /vendor/exface/Core/index.php;
+
+ if (!-e $request_filename){
+ rewrite ^/[^/]*$ /vendor/exface/Core/index.php;
+ }
+}
+
+location /config {
+ return 403;
+}
+
+location /backup {
+ return 403;
+}
+
+location /translations {
+ return 403;
+}
+
+location /logs {
+ return 403;
+}
+
+location ~ ^/data/\..*$ {
+ return 403;
+}
+
+location ~* ^vendor/.*\.html$ {
+ return 403;
+}
+
+location ~* ^vendor/.*/gh-pages.*$ {
+ return 403;
+}
+
+# Disable .git directory
+location ~ /\.git {
+ deny all;
+ access_log off;
+ log_not_found off;
+}
+
+# Add locations of phpmyadmin here.
+location ~ [^/]\.php(/|$) {
+ fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
+ fastcgi_pass 127.0.0.1:9000;
+ include fastcgi_params;
+ fastcgi_param HTTP_PROXY "";
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ fastcgi_param PATH_INFO $fastcgi_path_info;
+ fastcgi_param QUERY_STRING $query_string;
+ fastcgi_intercept_errors on;
+ fastcgi_connect_timeout 300;
+ fastcgi_send_timeout 3600;
+ fastcgi_read_timeout 3600;
+ fastcgi_buffer_size 128k;
+ fastcgi_buffers 4 256k;
+ fastcgi_busy_buffers_size 256k;
+ fastcgi_temp_file_write_size 256k;
+}';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getConfigFileName(): string
+ {
+ return 'nginx.conf';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getConfigTemplatePathRelative(): string
+ {
+ return 'default.nginx.conf';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function stringToComment(string $comment): string
+ {
+ return "# {$comment}";
+ }
+}
\ No newline at end of file
diff --git a/Install/default.nginx.conf b/Install/default.nginx.conf
new file mode 100644
index 000000000..7a553be1e
--- /dev/null
+++ b/Install/default.nginx.conf
@@ -0,0 +1,23 @@
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ # Your main server block:
+ server {
+ listen 80;
+ server_name _;
+
+ root /site/wwwroot/powerui/current;
+
+ # Include folder-specific routing rules
+ include /site/wwwroot/powerui/current/nginx/*.conf;
+
+ location / {
+ try_files $uri $uri/ /index.php?$query_string;
+ }
+
+ # BEGIN Locations
+
+ # END Locations
+ }
+}
\ No newline at end of file
From 460932a316887a0b1d0de0fd4f29460b88869621 Mon Sep 17 00:00:00 2001
From: hail-cookies <81558994+hail-cookies@users.noreply.github.com>
Date: Wed, 26 Nov 2025 15:13:14 +0100
Subject: [PATCH 4/9] DEV FileContentsInstaller improved error for missing
markers
---
CommonLogic/AppInstallers/FileContentInstaller.php | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/CommonLogic/AppInstallers/FileContentInstaller.php b/CommonLogic/AppInstallers/FileContentInstaller.php
index 722059942..d5c974f3f 100644
--- a/CommonLogic/AppInstallers/FileContentInstaller.php
+++ b/CommonLogic/AppInstallers/FileContentInstaller.php
@@ -151,7 +151,13 @@ public function install(string $source_absolute_path) : \Iterator
break;
// Error.
default:
- throw new InstallerRuntimeError($this, "Failed to find marker \"{$marker}\" in file \"{$this->getFilePathAbsolute()}\".");
+ $requiredMarkers = json_encode(array_keys($this->contentArray));
+ throw new InstallerRuntimeError(
+ $this,
+ "Failed to find marker \"{$marker}\" in file \"{$this->getFilePathAbsolute()}\"." .
+ " Both \"{$this->getMarkerBegin()}\" and \"{$this->getMarkerEnd()}\" for the following" .
+ " markers should be present in that file: " . $requiredMarkers . "."
+ );
}
}
}
From 2042628b16b3dfa0f222e35eee87fd9a4b105964 Mon Sep 17 00:00:00 2001
From: hail-cookies <81558994+hail-cookies@users.noreply.github.com>
Date: Thu, 27 Nov 2025 11:35:10 +0100
Subject: [PATCH 5/9] DEV CoreApp improved automatic server type detection
---
CoreApp.php | 53 +++++++++++++++++++++++-----
DataTypes/ServerSoftwareDataType.php | 10 ++++--
2 files changed, 51 insertions(+), 12 deletions(-)
diff --git a/CoreApp.php b/CoreApp.php
index fecf683d1..c858732f7 100644
--- a/CoreApp.php
+++ b/CoreApp.php
@@ -3,6 +3,8 @@
use exface\Core\CommonLogic\AppInstallers\ApacheServerInstaller;
use exface\Core\CommonLogic\AppInstallers\AppDocsInstaller;
+use exface\Core\CommonLogic\AppInstallers\NginxServerInstaller;
+use exface\Core\Exceptions\Installers\InstallerRuntimeError;
use exface\Core\Facades\PermalinkFacade;
use exface\Core\Interfaces\InstallerInterface;
use exface\Core\Factories\ConfigurationFactory;
@@ -23,6 +25,7 @@
class CoreApp extends App
{
+ const CONFIG_SERVER_INSTALLER = 'INSTALLER.SERVER_INSTALLER.CLASS';
const CONFIG_FILENAME_SYSTEM = 'System';
private $config_loading = false;
@@ -102,11 +105,19 @@ public function getInstaller(InstallerInterface $injected_installer = null)
$tplInstaller->setFacade(FacadeFactory::createFromString(PermalinkFacade::class, $this->getWorkbench()));
$installer->addInstaller($tplInstaller);
- // Server installer (e.g. for Microsoft IIS)
+ // Server installer.
$serverInstallerClass = $this->getServerInstallerClass();
if ($serverInstallerClass !== null) {
$serverInstaller = new $serverInstallerClass($this->getSelector());
$installer->addInstaller($serverInstaller);
+ } else {
+ $msg = 'Could not determine server installer class! Consider defining the server installer class ' .
+ 'explicitly by setting "' . self::CONFIG_SERVER_INSTALLER . '" in "System.config.json".';
+
+ throw new InstallerRuntimeError(
+ $installer,
+ $msg
+ );
}
// Scheduler
@@ -127,15 +138,39 @@ public function getInstaller(InstallerInterface $injected_installer = null)
*/
protected function getServerInstallerClass() : string|null
{
- $serverInstallerClass = $this->getWorkbench()->getConfig()->getOption("INSTALLER.SERVER_INSTALLER.CLASS");
+ // From config option.
+ $cfg = $this->getWorkbench()->getConfig();
+ if($cfg->hasOption(self::CONFIG_SERVER_INSTALLER)) {
+ $configOption = $this->getWorkbench()->getConfig()->getOption(self::CONFIG_SERVER_INSTALLER);
+
+ if(!empty($configOption)) {
+ return $configOption;
+ }
+ }
- // Autodetect server installer if not set explicitly
- return match (true) {
- !empty($serverInstallerClass) => $serverInstallerClass,
- ServerSoftwareDataType::isServerIIS() => '\\' . ltrim(IISServerInstaller::class, "\\"),
- ServerSoftwareDataType::isServerNginx() => null,
- ServerSoftwareDataType::isServerApache() => '\\' . ltrim(ApacheServerInstaller::class, "\\"),
- default => null,
+ // Read from PHP constant.
+ $softwareFamily = ServerSoftwareDataType::getServerSoftwareFamily();
+
+ // Guess via folder structure.
+ if(empty($softwareFamily)) {
+ $path = $this->getWorkbench()->getInstallationPath();
+ $softwareFamily = match (true) {
+ preg_match("/\\\\www\\\\/", $path) === 1 => ServerSoftwareDataType::SERVER_SOFTWARE_APACHE,
+ preg_match("/[Cc]:inetpub\\\\/", $path) === 1 => ServerSoftwareDataType::SERVER_SOFTWARE_IIS,
+ preg_match("/\\\\wwwroot\\\\/", $path) === 1 => ServerSoftwareDataType::SERVER_SOFTWARE_NGINX,
+ default => null
+ };
+
+ if(empty($softwareFamily)) {
+ return null;
+ }
+ }
+
+ return match ($softwareFamily) {
+ ServerSoftwareDataType::SERVER_SOFTWARE_APACHE => '\\' . ltrim(ApacheServerInstaller::class, "\\"),
+ ServerSoftwareDataType::SERVER_SOFTWARE_IIS => '\\' . ltrim(IISServerInstaller::class, "\\"),
+ ServerSoftwareDataType::SERVER_SOFTWARE_NGINX => '\\' . ltrim(NginxServerInstaller::class, "\\"),
+ default => null
};
}
diff --git a/DataTypes/ServerSoftwareDataType.php b/DataTypes/ServerSoftwareDataType.php
index 739d31dc7..dac2829c1 100644
--- a/DataTypes/ServerSoftwareDataType.php
+++ b/DataTypes/ServerSoftwareDataType.php
@@ -11,6 +11,10 @@
*/
class ServerSoftwareDataType extends StringDataType
{
+ const SERVER_SOFTWARE_APACHE = 'Apache';
+ const SERVER_SOFTWARE_IIS = 'Microsoft-IIS';
+ const SERVER_SOFTWARE_NGINX = 'nginx';
+
/**
* Returns the full name of the servers software, that runs the workbench
*
@@ -75,7 +79,7 @@ public static function getServerSoftwareVersion() : ?string
*/
public static function isServerIIS() : bool
{
- return strcasecmp(static::getServerSoftwareFamily(), 'Microsoft-IIS') === 0;
+ return strcasecmp(static::getServerSoftwareFamily(), self::SERVER_SOFTWARE_IIS) === 0;
}
/**
@@ -84,7 +88,7 @@ public static function isServerIIS() : bool
*/
public static function isServerApache() : bool
{
- return strcasecmp(static::getServerSoftwareFamily(), 'Apache') === 0;
+ return strcasecmp(static::getServerSoftwareFamily(), self::SERVER_SOFTWARE_APACHE) === 0;
}
/**
@@ -93,7 +97,7 @@ public static function isServerApache() : bool
*/
public static function isServerNginx() : bool
{
- return strcasecmp(static::getServerSoftwareFamily(), 'nginx') === 0;
+ return strcasecmp(static::getServerSoftwareFamily(), self::SERVER_SOFTWARE_NGINX) === 0;
}
/**
From d83d29f94d404f9206259fbaa72bbc87dc73e767 Mon Sep 17 00:00:00 2001
From: hail-cookies <81558994+hail-cookies@users.noreply.github.com>
Date: Fri, 28 Nov 2025 17:40:22 +0100
Subject: [PATCH 6/9] DEV AppInstallerContainer can now inject messages
---
.../AppInstallers/AbstractServerInstaller.php | 18 +++-
.../AppInstallers/ApacheServerInstaller.php | 8 ++
.../AppInstallers/AppDebugInstaller.php | 101 ++++++++++++++++++
.../AppInstallers/AppInstallerContainer.php | 12 +++
.../AppInstallers/IISServerInstaller.php | 8 ++
.../AppInstallers/NginxServerInstaller.php | 8 ++
CoreApp.php | 52 ++++++---
Interfaces/InstallerContainerInterface.php | 14 ++-
8 files changed, 202 insertions(+), 19 deletions(-)
create mode 100644 CommonLogic/AppInstallers/AppDebugInstaller.php
diff --git a/CommonLogic/AppInstallers/AbstractServerInstaller.php b/CommonLogic/AppInstallers/AbstractServerInstaller.php
index 86fcd84df..a2ec8b51e 100644
--- a/CommonLogic/AppInstallers/AbstractServerInstaller.php
+++ b/CommonLogic/AppInstallers/AbstractServerInstaller.php
@@ -57,8 +57,13 @@ protected abstract function getConfigTemplatePathRelative() : string;
* @return string
*/
protected abstract function stringToComment(string $comment) : string;
-
+
/**
+ * @return string
+ */
+ protected abstract function ServerSoftwareFamilyDefault() : string;
+
+ /**
*
* {@inheritDoc}
* @see \exface\Core\Interfaces\InstallerInterface::backup()
@@ -87,10 +92,15 @@ public function install(string $source_absolute_path): \Iterator
{
$indentOuter = $this->getOutputIndentation();
$indent = $indentOuter . $indentOuter;
- $serverType = ServerSoftwareDataType::getServerSoftwareFamily() ?? 'UNKNOWN SERVER SOFTWARE';
- $serverVersion = ServerSoftwareDataType::getServerSoftwareVersion() ?? 'UNKNOWN VERSION';
- yield $indentOuter . "Server configuration for {$serverType} {$serverVersion}:" . PHP_EOL;
+ $serverType = ServerSoftwareDataType::getServerSoftwareFamily() ??
+ $this->ServerSoftwareFamilyDefault() ??
+ 'UNKNOWN SERVER SOFTWARE';
+
+ $serverVersion = ServerSoftwareDataType::getServerSoftwareVersion() ??
+ 'with unknown version';
+
+ yield $indentOuter . "Loading server configuration for \"{$serverType}\" {$serverVersion}:" . PHP_EOL;
$this->configInstaller->setOutputIndentation($indent);
yield $indent . "Using \"{$this->getConfigTemplatePathRelative()}\" template for {$serverType}." . PHP_EOL;
diff --git a/CommonLogic/AppInstallers/ApacheServerInstaller.php b/CommonLogic/AppInstallers/ApacheServerInstaller.php
index 48cd4f3a1..de0db424d 100644
--- a/CommonLogic/AppInstallers/ApacheServerInstaller.php
+++ b/CommonLogic/AppInstallers/ApacheServerInstaller.php
@@ -97,4 +97,12 @@ protected function stringToComment(string $comment): string
{
return "# {$comment}";
}
+
+ /**
+ * @inheritDoc
+ */
+ protected function ServerSoftwareFamilyDefault(): string
+ {
+ return 'Apache';
+ }
}
\ No newline at end of file
diff --git a/CommonLogic/AppInstallers/AppDebugInstaller.php b/CommonLogic/AppInstallers/AppDebugInstaller.php
new file mode 100644
index 000000000..940fbe13f
--- /dev/null
+++ b/CommonLogic/AppInstallers/AppDebugInstaller.php
@@ -0,0 +1,101 @@
+indent = $indent;
+ $this->message = str_ends_with($message, PHP_EOL) ? $message : $message . PHP_EOL;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function install(string $source_absolute_path): \Iterator
+ {
+ yield $this->getMessage() ?? new \EmptyIterator();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function backup(string $absolute_path): \Iterator
+ {
+ yield $this->getMessage() ?? new \EmptyIterator();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function uninstall(): \Iterator
+ {
+ yield $this->getMessage() ?? new \EmptyIterator();
+ }
+
+ /**
+ * STUB! Do not use.
+ * @deprecated
+ */
+ public function getWorkbench() : WorkbenchInterface
+ {
+ throw new NotImplementedError('Method "getWorkbench()" is only a stub in "' . self::class . '"!');
+ }
+
+ /**
+ * STUB! Do not use.
+ * @deprecated
+ */
+ public function getApp() : WorkbenchInterface
+ {
+ throw new NotImplementedError('Method "getApp()" is only a stub in "' . self::class . '"!');
+ }
+
+ /**
+ * @return string
+ */
+ public function getOutputIndentation() : string
+ {
+ return $this->indent;
+ }
+
+ /**
+ * @param string $value
+ * @return $this
+ */
+ public function setOutputIndentation(string $value) : AppDebugInstaller
+ {
+ $this->indent = $value;
+ return $this;
+ }
+
+ /**
+ * @param bool $withIndentation
+ * @return string|null
+ */
+ public function getMessage(bool $withIndentation = true) : ?string
+ {
+ if($this->message === null) {
+ return null;
+ }
+
+ return $this->getOutputIndentation() . $this->message;
+ }
+}
\ No newline at end of file
diff --git a/CommonLogic/AppInstallers/AppInstallerContainer.php b/CommonLogic/AppInstallers/AppInstallerContainer.php
index e69225254..d00ee527e 100644
--- a/CommonLogic/AppInstallers/AppInstallerContainer.php
+++ b/CommonLogic/AppInstallers/AppInstallerContainer.php
@@ -161,4 +161,16 @@ public function extract(callable $filterCallback) : InstallerContainerInterface
}
return $container;
}
+
+ /**
+ * @inheritDoc
+ */
+ public function addMessage(string $message) : InstallerContainerInterface
+ {
+ if($message !== '') {
+ $this->addInstaller(new AppDebugInstaller($message));
+ }
+
+ return $this;
+ }
}
\ No newline at end of file
diff --git a/CommonLogic/AppInstallers/IISServerInstaller.php b/CommonLogic/AppInstallers/IISServerInstaller.php
index 2ae2c1ba9..6b3988245 100644
--- a/CommonLogic/AppInstallers/IISServerInstaller.php
+++ b/CommonLogic/AppInstallers/IISServerInstaller.php
@@ -82,4 +82,12 @@ protected function stringToComment(string $comment): string
{
return "";
}
+
+ /**
+ * @inheritDoc
+ */
+ protected function ServerSoftwareFamilyDefault(): string
+ {
+ return 'Microsoft-IIS';
+ }
}
\ No newline at end of file
diff --git a/CommonLogic/AppInstallers/NginxServerInstaller.php b/CommonLogic/AppInstallers/NginxServerInstaller.php
index 8f7a1b50e..71aea4326 100644
--- a/CommonLogic/AppInstallers/NginxServerInstaller.php
+++ b/CommonLogic/AppInstallers/NginxServerInstaller.php
@@ -87,4 +87,12 @@ protected function stringToComment(string $comment): string
{
return "# {$comment}";
}
+
+ /**
+ * @inheritDoc
+ */
+ protected function ServerSoftwareFamilyDefault(): string
+ {
+ return 'nginx';
+ }
}
\ No newline at end of file
diff --git a/CoreApp.php b/CoreApp.php
index 2ef31b6c1..8cda457de 100644
--- a/CoreApp.php
+++ b/CoreApp.php
@@ -3,8 +3,9 @@
use exface\Core\CommonLogic\AppInstallers\ApacheServerInstaller;
use exface\Core\CommonLogic\AppInstallers\AppDocsInstaller;
+use exface\Core\CommonLogic\AppInstallers\AppInstallerContainer;
+use exface\Core\CommonLogic\AppInstallers\AppDebugInstaller;
use exface\Core\CommonLogic\AppInstallers\NginxServerInstaller;
-use exface\Core\Exceptions\Installers\InstallerRuntimeError;
use exface\Core\Facades\PermalinkFacade;
use exface\Core\Interfaces\InstallerInterface;
use exface\Core\Factories\ConfigurationFactory;
@@ -106,17 +107,14 @@ public function getInstaller(InstallerInterface $injected_installer = null)
$installer->addInstaller($tplInstaller);
// Server installer.
- $serverInstallerClass = $this->getServerInstallerClass();
+ $serverInstallerClass = $this->getServerInstallerClass($installer);
if ($serverInstallerClass !== null) {
$serverInstaller = new $serverInstallerClass($this->getSelector());
$installer->addInstaller($serverInstaller);
} else {
- $msg = 'Could not determine server installer class! Consider defining the server installer class ' .
- 'explicitly by setting "' . self::CONFIG_SERVER_INSTALLER . '" in "System.config.json".';
-
- throw new InstallerRuntimeError(
- $installer,
- $msg
+ $installer->addMessage('FAILED - Could not determine server installer class! Consider defining the' .
+ ' server installer class explicitly by setting "' . self::CONFIG_SERVER_INSTALLER .
+ '" in "System.config.json".'
);
}
@@ -134,18 +132,28 @@ public function getInstaller(InstallerInterface $injected_installer = null)
}
/**
+ * @param AppInstallerContainer $installer
* @return string|null
*/
- protected function getServerInstallerClass() : string|null
+ protected function getServerInstallerClass(AppInstallerContainer $installer) : string|null
{
+ $installer->addMessage('Determining server installer class:');
+ $indent = ' ';
+
// From config option.
$cfg = $this->getWorkbench()->getConfig();
if($cfg->hasOption(self::CONFIG_SERVER_INSTALLER)) {
$configOption = $this->getWorkbench()->getConfig()->getOption(self::CONFIG_SERVER_INSTALLER);
-
- if(!empty($configOption)) {
+
+ // Valid-ish config option.
+ if(class_exists($configOption)) {
+ $installer->addMessage($indent . 'Found installer class in config: "' . $configOption . '".');
return $configOption;
- }
+ }
+
+ // Invalid config option.
+ $installer->addMessage($indent . 'Value "' . $configOption . '" for config option "' .
+ self::CONFIG_SERVER_INSTALLER . '" is not a valid class name.');
}
// Read from PHP constant.
@@ -155,6 +163,9 @@ protected function getServerInstallerClass() : string|null
// Future installations should have a manually defined ``
if(empty($softwareFamily)) {
$path = $this->getWorkbench()->getInstallationPath();
+ $installer->addMessage($indent . 'Deducing server software from installation path "' . $path .
+ '" under "' . php_uname('s') . '".');
+
$softwareFamily = match (true) {
// Microsoft IIS runs on windows and has its files mostly in c:\inetpub\wwwroot
ServerSoftwareDataType::isOsWindows() && preg_match('/[Cc]:\\\\inetpub\\\\wwwroot\\\\/', $path) === 1 => ServerSoftwareDataType::SERVER_SOFTWARE_IIS,
@@ -165,18 +176,31 @@ protected function getServerInstallerClass() : string|null
ServerSoftwareDataType::isOsLinux() && preg_match("/\/www\//", $path) === 1 => ServerSoftwareDataType::SERVER_SOFTWARE_APACHE,
default => null
};
-
+
if(empty($softwareFamily)) {
+ $installer->addMessage($indent . 'Could not determine server software.');
return null;
+ } else {
+ $installer->addMessage($indent . 'Server software from folder structure: "' . $softwareFamily . '".');
}
+ } else {
+ $installer->addMessage($indent . 'Server software from PHP constant: "' . $softwareFamily . '".');
}
- return match ($softwareFamily) {
+ $class = match ($softwareFamily) {
ServerSoftwareDataType::SERVER_SOFTWARE_APACHE => '\\' . ltrim(ApacheServerInstaller::class, "\\"),
ServerSoftwareDataType::SERVER_SOFTWARE_IIS => '\\' . ltrim(IISServerInstaller::class, "\\"),
ServerSoftwareDataType::SERVER_SOFTWARE_NGINX => '\\' . ltrim(NginxServerInstaller::class, "\\"),
default => null
};
+
+ if($class !== null) {
+ $installer->addMessage($indent . 'Deduced installer class from server software: "' . $class . '".');
+ } else {
+ $installer->addMessage($indent . 'Could not deduce server installer class.');
+ }
+
+ return $class;
}
/**
diff --git a/Interfaces/InstallerContainerInterface.php b/Interfaces/InstallerContainerInterface.php
index 6ec1db63e..df42f6a59 100644
--- a/Interfaces/InstallerContainerInterface.php
+++ b/Interfaces/InstallerContainerInterface.php
@@ -17,7 +17,7 @@ public function addInstaller(InstallerInterface $installer, $insertAtBeinning =
* @return InstallerInterface[]
*/
public function getInstallers() : array;
-
+
/**
* Returns a new installer container with only installers matching the ginve filter
*
@@ -25,4 +25,16 @@ public function getInstallers() : array;
* @return InstallerContainerInterface
*/
public function extract(callable $filterCallback) : InstallerContainerInterface;
+
+ /**
+ * Adds a message to be displayed when performing `install()`, `backup()` or `uninstall()`.
+ *
+ * NOTE: Adding a message is similar to adding an installer. Both use the same queue. Adding a message
+ * just before adding an installer means, that this message will always be displayed before that installer
+ * is executed.
+ *
+ * @param string $message
+ * @return InstallerContainerInterface
+ */
+ public function addMessage(string $message) : InstallerContainerInterface;
}
\ No newline at end of file
From b197b16d4d3ca45e0029e55d64970f76f48849eb Mon Sep 17 00:00:00 2001
From: hail-cookies <81558994+hail-cookies@users.noreply.github.com>
Date: Tue, 2 Dec 2025 09:53:57 +0100
Subject: [PATCH 7/9] DEV AppInstallerContainer simplified addMessage
---
.../AppInstallers/AppInstallerContainer.php | 19 +++++++++++++++--
...pDebugInstaller.php => DebugInstaller.php} | 21 +++++++------------
CoreApp.php | 1 -
3 files changed, 24 insertions(+), 17 deletions(-)
rename CommonLogic/AppInstallers/{AppDebugInstaller.php => DebugInstaller.php} (77%)
diff --git a/CommonLogic/AppInstallers/AppInstallerContainer.php b/CommonLogic/AppInstallers/AppInstallerContainer.php
index d00ee527e..77dc6dd91 100644
--- a/CommonLogic/AppInstallers/AppInstallerContainer.php
+++ b/CommonLogic/AppInstallers/AppInstallerContainer.php
@@ -44,6 +44,11 @@ public final function install(string $source_absolute_path) : \Iterator
$eventMgr = $this->getWorkbench()->eventManager();
foreach ($this->getInstallers() as $installer) {
+ if($installer instanceof DebugInstaller) {
+ $installer->install($source_absolute_path);
+ continue;
+ }
+
$eventMgr->dispatch(new OnBeforeInstallEvent($installer, $source_absolute_path));
yield from $installer->install($source_absolute_path);
$eventMgr->dispatch(new OnInstallEvent($installer, $source_absolute_path));
@@ -93,6 +98,11 @@ public final function backup(string $destination_absolute_path) : \Iterator
$eventMgr = $this->getWorkbench()->eventManager();
foreach ($this->getInstallers() as $installer) {
+ if($installer instanceof DebugInstaller) {
+ $installer->backup($destination_absolute_path);
+ continue;
+ }
+
$eventMgr->dispatch(new OnBeforeBackupEvent($installer, $destination_absolute_path));
yield from $installer->backup($destination_absolute_path);
$eventMgr->dispatch(new OnBackupEvent($installer, $destination_absolute_path));
@@ -117,6 +127,11 @@ public final function uninstall() : \Iterator
// TODO disable mutations here too???
$eventMgr = $this->getWorkbench()->eventManager();
foreach (array_reverse($this->getInstallers()) as $installer) {
+ if($installer instanceof DebugInstaller) {
+ $installer->uninstall();
+ continue;
+ }
+
$eventMgr->dispatch(new OnBeforeUninstallEvent($installer));
yield from $installer->uninstall();
$eventMgr->dispatch(new OnUninstallEvent($installer));
@@ -165,10 +180,10 @@ public function extract(callable $filterCallback) : InstallerContainerInterface
/**
* @inheritDoc
*/
- public function addMessage(string $message) : InstallerContainerInterface
+ public function addMessage(string $message, bool $insertAtBeginning = false) : InstallerContainerInterface
{
if($message !== '') {
- $this->addInstaller(new AppDebugInstaller($message));
+ $this->addInstaller(new DebugInstaller($this->getWorkbench(), $message), $insertAtBeginning);
}
return $this;
diff --git a/CommonLogic/AppInstallers/AppDebugInstaller.php b/CommonLogic/AppInstallers/DebugInstaller.php
similarity index 77%
rename from CommonLogic/AppInstallers/AppDebugInstaller.php
rename to CommonLogic/AppInstallers/DebugInstaller.php
index 940fbe13f..e33e8ca49 100644
--- a/CommonLogic/AppInstallers/AppDebugInstaller.php
+++ b/CommonLogic/AppInstallers/DebugInstaller.php
@@ -2,8 +2,7 @@
namespace exface\Core\CommonLogic\AppInstallers;
-use exface\Core\Exceptions\NotImplementedError;
-use exface\Core\Interfaces\AppInstallerInterface;
+use exface\Core\Interfaces\InstallerInterface;
use exface\Core\Interfaces\WorkbenchInterface;
/**
@@ -12,16 +11,19 @@
*
* You can use this installer as a stub or to inject debugging messages and logging into your deployment logic.
*/
-class AppDebugInstaller implements AppInstallerInterface
+class DebugInstaller implements InstallerInterface
{
+ private WorkbenchInterface $workbench;
private ?string $message;
private string $indent;
function __construct(
+ WorkbenchInterface $workbench,
string $message = null,
string $indent = ' '
)
{
+ $this->workbench = $workbench;
$this->indent = $indent;
$this->message = str_ends_with($message, PHP_EOL) ? $message : $message . PHP_EOL;
}
@@ -56,16 +58,7 @@ public function uninstall(): \Iterator
*/
public function getWorkbench() : WorkbenchInterface
{
- throw new NotImplementedError('Method "getWorkbench()" is only a stub in "' . self::class . '"!');
- }
-
- /**
- * STUB! Do not use.
- * @deprecated
- */
- public function getApp() : WorkbenchInterface
- {
- throw new NotImplementedError('Method "getApp()" is only a stub in "' . self::class . '"!');
+ return $this->workbench;
}
/**
@@ -80,7 +73,7 @@ public function getOutputIndentation() : string
* @param string $value
* @return $this
*/
- public function setOutputIndentation(string $value) : AppDebugInstaller
+ public function setOutputIndentation(string $value) : DebugInstaller
{
$this->indent = $value;
return $this;
diff --git a/CoreApp.php b/CoreApp.php
index 8cda457de..927363180 100644
--- a/CoreApp.php
+++ b/CoreApp.php
@@ -4,7 +4,6 @@
use exface\Core\CommonLogic\AppInstallers\ApacheServerInstaller;
use exface\Core\CommonLogic\AppInstallers\AppDocsInstaller;
use exface\Core\CommonLogic\AppInstallers\AppInstallerContainer;
-use exface\Core\CommonLogic\AppInstallers\AppDebugInstaller;
use exface\Core\CommonLogic\AppInstallers\NginxServerInstaller;
use exface\Core\Facades\PermalinkFacade;
use exface\Core\Interfaces\InstallerInterface;
From fa1c0757df8075db0ac5feedc945f03657a936c8 Mon Sep 17 00:00:00 2001
From: hail-cookies <81558994+hail-cookies@users.noreply.github.com>
Date: Tue, 2 Dec 2025 09:55:30 +0100
Subject: [PATCH 8/9] FIX AppInstallerContainer fixed syntax
---
CommonLogic/AppInstallers/AppInstallerContainer.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/CommonLogic/AppInstallers/AppInstallerContainer.php b/CommonLogic/AppInstallers/AppInstallerContainer.php
index 77dc6dd91..5f5694ea1 100644
--- a/CommonLogic/AppInstallers/AppInstallerContainer.php
+++ b/CommonLogic/AppInstallers/AppInstallerContainer.php
@@ -45,7 +45,7 @@ public final function install(string $source_absolute_path) : \Iterator
$eventMgr = $this->getWorkbench()->eventManager();
foreach ($this->getInstallers() as $installer) {
if($installer instanceof DebugInstaller) {
- $installer->install($source_absolute_path);
+ yield from $installer->install($source_absolute_path);
continue;
}
@@ -99,7 +99,7 @@ public final function backup(string $destination_absolute_path) : \Iterator
$eventMgr = $this->getWorkbench()->eventManager();
foreach ($this->getInstallers() as $installer) {
if($installer instanceof DebugInstaller) {
- $installer->backup($destination_absolute_path);
+ yield from $installer->backup($destination_absolute_path);
continue;
}
@@ -128,7 +128,7 @@ public final function uninstall() : \Iterator
$eventMgr = $this->getWorkbench()->eventManager();
foreach (array_reverse($this->getInstallers()) as $installer) {
if($installer instanceof DebugInstaller) {
- $installer->uninstall();
+ yield from $installer->uninstall();
continue;
}
From 56b896946d520e714fc745e4dfe2365026f238f8 Mon Sep 17 00:00:00 2001
From: hail-cookies <81558994+hail-cookies@users.noreply.github.com>
Date: Tue, 2 Dec 2025 10:55:48 +0100
Subject: [PATCH 9/9] DEV DebugInstaller now implements IAmSilentInterface for
better readability
---
.../AppInstallers/AppInstallerContainer.php | 7 ++++---
CommonLogic/AppInstallers/DebugInstaller.php | 3 ++-
Interfaces/IAmSilentInterface.php | 18 ++++++++++++++++++
3 files changed, 24 insertions(+), 4 deletions(-)
create mode 100644 Interfaces/IAmSilentInterface.php
diff --git a/CommonLogic/AppInstallers/AppInstallerContainer.php b/CommonLogic/AppInstallers/AppInstallerContainer.php
index 5f5694ea1..3e52a633d 100644
--- a/CommonLogic/AppInstallers/AppInstallerContainer.php
+++ b/CommonLogic/AppInstallers/AppInstallerContainer.php
@@ -3,6 +3,7 @@
use exface\Core\DataTypes\DateTimeDataType;
use exface\Core\Interfaces\AppInstallerInterface;
+use exface\Core\Interfaces\IAmSilentInterface;
use exface\Core\Interfaces\InstallerInterface;
use exface\Core\Interfaces\InstallerContainerInterface;
use exface\Core\Events\Installer\OnBeforeInstallEvent;
@@ -44,7 +45,7 @@ public final function install(string $source_absolute_path) : \Iterator
$eventMgr = $this->getWorkbench()->eventManager();
foreach ($this->getInstallers() as $installer) {
- if($installer instanceof DebugInstaller) {
+ if($installer instanceof IAmSilentInterface) {
yield from $installer->install($source_absolute_path);
continue;
}
@@ -98,7 +99,7 @@ public final function backup(string $destination_absolute_path) : \Iterator
$eventMgr = $this->getWorkbench()->eventManager();
foreach ($this->getInstallers() as $installer) {
- if($installer instanceof DebugInstaller) {
+ if($installer instanceof IAmSilentInterface) {
yield from $installer->backup($destination_absolute_path);
continue;
}
@@ -127,7 +128,7 @@ public final function uninstall() : \Iterator
// TODO disable mutations here too???
$eventMgr = $this->getWorkbench()->eventManager();
foreach (array_reverse($this->getInstallers()) as $installer) {
- if($installer instanceof DebugInstaller) {
+ if($installer instanceof IAmSilentInterface) {
yield from $installer->uninstall();
continue;
}
diff --git a/CommonLogic/AppInstallers/DebugInstaller.php b/CommonLogic/AppInstallers/DebugInstaller.php
index e33e8ca49..3522e9a0f 100644
--- a/CommonLogic/AppInstallers/DebugInstaller.php
+++ b/CommonLogic/AppInstallers/DebugInstaller.php
@@ -3,6 +3,7 @@
namespace exface\Core\CommonLogic\AppInstallers;
use exface\Core\Interfaces\InstallerInterface;
+use exface\Core\Interfaces\IAmSilentInterface;
use exface\Core\Interfaces\WorkbenchInterface;
/**
@@ -11,7 +12,7 @@
*
* You can use this installer as a stub or to inject debugging messages and logging into your deployment logic.
*/
-class DebugInstaller implements InstallerInterface
+class DebugInstaller implements InstallerInterface, IAmSilentInterface
{
private WorkbenchInterface $workbench;
private ?string $message;
diff --git a/Interfaces/IAmSilentInterface.php b/Interfaces/IAmSilentInterface.php
new file mode 100644
index 000000000..0a4157562
--- /dev/null
+++ b/Interfaces/IAmSilentInterface.php
@@ -0,0 +1,18 @@
+