You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
384 lines
13 KiB
384 lines
13 KiB
<?php
|
|
|
|
/**
|
|
* SPDX-License-Identifier: EUPL-1.2
|
|
* Authors: see /README.md
|
|
*/
|
|
|
|
namespace SeaCMS;
|
|
|
|
use Exception;
|
|
use Pico;
|
|
use SeacmsAppPlugin;
|
|
use SeaCMS\Api\SpecialOutputException;
|
|
use SeaCMS\App\TestInterface;
|
|
use Throwable;
|
|
|
|
set_error_handler(function (
|
|
int $errno,
|
|
string $errstr,
|
|
string $errfile = '',
|
|
int $errline = -1
|
|
) {
|
|
if (!isset($GLOBALS['errorMessages'])){
|
|
$GLOBALS['errorMessages']= [];
|
|
}
|
|
$GLOBALS['errorMessages'][] = <<<HTML
|
|
<div>
|
|
Error : $errstr<br>
|
|
in file '$errfile' (line '$errline').<br/>
|
|
ErrCode : $errno
|
|
</div>
|
|
|
|
HTML;
|
|
return true;
|
|
});
|
|
|
|
class App
|
|
{
|
|
/**
|
|
* plugins path in vendor
|
|
* @var string
|
|
*/
|
|
public const PLUGINS_PATH = 'vendor/picocms/plugins/';
|
|
/**
|
|
* themes path in vendor
|
|
* @var string
|
|
*/
|
|
public const THEMES_PATH = 'vendor/picocms/themes/';
|
|
/**
|
|
* Pico instance.
|
|
* @var Pico
|
|
*/
|
|
protected $pico;
|
|
|
|
public function __construct(string $contentFolderFromRoot, ?TestInterface $testRunner = null)
|
|
{
|
|
// sanitize content folder
|
|
$cwd = getcwd();
|
|
if (empty($contentFolderFromRoot)){
|
|
$contentFolderFromRoot = 'content';
|
|
} else {
|
|
$contentFolderFromRoot = str_replace('\\','/',$contentFolderFromRoot);
|
|
}
|
|
if (!is_dir($cwd)){
|
|
throw new Exception("getcwd returned a path that is not a directory !");
|
|
}
|
|
if (!is_dir("$cwd/$contentFolderFromRoot")){
|
|
$contentFolderFromRoot = 'vendor/picocms/pico/content-sample';
|
|
}
|
|
if (substr($contentFolderFromRoot,-1) !== '/'){
|
|
$contentFolderFromRoot .= '/';
|
|
}
|
|
// instance Pico
|
|
$this->pico = new Pico(
|
|
$cwd, // root dir
|
|
$contentFolderFromRoot, // config dir
|
|
self::PLUGINS_PATH, // plugins dir
|
|
self::THEMES_PATH // themes dir
|
|
);
|
|
$this->pico->loadPlugin(new SeacmsAppPlugin($this->pico, $testRunner));
|
|
$this->update_SERVERIfNeeded($this->pico, $contentFolderFromRoot);
|
|
}
|
|
|
|
public function runPico(): string
|
|
{
|
|
return $this->pico->run();
|
|
}
|
|
|
|
/**
|
|
* instanciate Pico and run it then echo output
|
|
* @param string $contentFolderFromRoot where is the root folder
|
|
*/
|
|
public static function run(string $contentFolderFromRoot)
|
|
{
|
|
try {
|
|
$app = new App($contentFolderFromRoot);
|
|
$output = $app->runPico();
|
|
self::appendErrorMessagesIfNeeded($output);
|
|
} catch (SpecialOutputException $th) {
|
|
$output = $th->getJsonResponse()->send();
|
|
} catch (Throwable $th) {
|
|
$output = <<<HTML
|
|
<div style="color:red;">
|
|
Exception : {$th->__toString()}
|
|
</div>
|
|
HTML;
|
|
self::appendErrorMessagesIfNeeded($output);
|
|
} finally {
|
|
echo $output;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* append error messages from $GLOBALS['errorMessages']
|
|
* only if $_GET['debug'] === 'yes'
|
|
* @param string &$output
|
|
*/
|
|
protected static function appendErrorMessagesIfNeeded(string &$output)
|
|
{
|
|
if (!empty($_GET['debug']) && $_GET['debug'] === 'yes' && !empty($GLOBALS['errorMessages'])){
|
|
$formattedMessages = implode("\n", $GLOBALS['errorMessages']);
|
|
$formattedMessages = <<<HTML
|
|
<div style="background-color:navajowhite;">
|
|
$formattedMessages
|
|
</div>
|
|
HTML;
|
|
if (preg_match('/<\/body>/i', $output, $match)) {
|
|
$output = str_replace($match[0], $formattedMessages.$match[0], $output);
|
|
} else {
|
|
$output = $output.$formattedMessages;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* detect if rewrite mode then update $_SERVER in consequence
|
|
* @see Pico:evaluateRequestUrl()
|
|
*
|
|
* @param Pico $pico
|
|
* @param string $configDir
|
|
*/
|
|
protected function update_SERVERIfNeeded(Pico $pico, string $configDir)
|
|
{
|
|
$data = [
|
|
'FROM_QUERY_STRING' => '',
|
|
'FROM_SCRIPT_NAME' => '',
|
|
'FROM_SCRIPT_FILENAME' => '',
|
|
'rootPath' => '/',
|
|
'rootPathFound' => false,
|
|
'rewriteModeactivated' => false,
|
|
'page' => 'index',
|
|
'continue' => true
|
|
];
|
|
$this
|
|
->extractRequestUrlFormQueryString($data)
|
|
->extractRequestUrlFromScriptFileName($data,$configDir)
|
|
->extractRequestUrlFromScriptName($data,$configDir)
|
|
->extractRootPathFromScriptNameIfNeeded($data,$configDir)
|
|
->definePage($data)
|
|
->setUrl($data, $configDir, $pico);
|
|
}
|
|
|
|
/**
|
|
* extract requestUrlFromQueryString
|
|
* @param array &$data
|
|
* @return self
|
|
*/
|
|
protected function extractRequestUrlFormQueryString(array &$data): self
|
|
{
|
|
if ($data['continue']){
|
|
// use QUERY_STRING; e.g. ?sub/page
|
|
$qString = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
|
|
if ($qString) {
|
|
$qString = strstr($qString, '&', true) ?: $qString;
|
|
if (strpos($qString, '=') === false) {
|
|
$data['FROM_QUERY_STRING'] = $qString;
|
|
}
|
|
}
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* extract requestUrlFromScriptName
|
|
* @param array &$data
|
|
* @param string $configDir
|
|
* @return self
|
|
*/
|
|
protected function extractRequestUrlFromScriptName(array &$data, string $configDir): self
|
|
{
|
|
if ($data['continue'] && !empty($_SERVER['SCRIPT_NAME']) && is_string($_SERVER['SCRIPT_NAME'])){
|
|
// use SCRIPT_NAME; e.g. /subfolder/content/sub/page/index.php
|
|
$matches = [];
|
|
$configDirForMatch = preg_quote($configDir,'/');
|
|
if (preg_match("/^(.*)$configDirForMatch(.*)(?!.php)(?:index.php)?$/",$_SERVER['SCRIPT_NAME'],$matches)){
|
|
$data['rootPath'] = $matches[1];
|
|
$data['rootPathFound'] = true;
|
|
$data['FROM_SCRIPT_NAME'] = $this->formatStringWithLeadingSlash($matches[2],false);
|
|
} elseif (!empty($data['FROM_SCRIPT_FILENAME']) && (
|
|
$this->isServerEndedBy("{$data['FROM_SCRIPT_FILENAME']}/index.php",'SCRIPT_NAME') ||
|
|
$this->isServerEndedBy("{$data['FROM_SCRIPT_FILENAME']}/",'SCRIPT_NAME')
|
|
)
|
|
){
|
|
$data['rootPath'] = $this->formatStringWithLeadingSlash($matches[2],false);
|
|
$data['rootPathFound'] = true;
|
|
$data['FROM_SCRIPT_NAME'] = $data['FROM_SCRIPT_FILENAME'];
|
|
$data['rewriteModeactivated'] = true;
|
|
}
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* extract requestUrlFromScriptFileName
|
|
* @param array &$data
|
|
* @param string $configDir
|
|
* @return self
|
|
*/
|
|
protected function extractRequestUrlFromScriptFileName(array &$data, string $configDir): self
|
|
{
|
|
if ($data['continue'] &&
|
|
!empty($_SERVER['SCRIPT_FILENAME']) &&
|
|
is_string($_SERVER['SCRIPT_FILENAME']) &&
|
|
substr($_SERVER['SCRIPT_FILENAME'],-strlen('index.php')) == 'index.php'){
|
|
// use SCRIPT_FILENAME; e.g. /var/www/subfolder/content/sub/page/index.php
|
|
|
|
// check if the current folder seems to correspond to root folder of seacms
|
|
if (is_dir('content') && is_dir('sites') && is_file('index.php')){
|
|
|
|
$cwd = realpath(getcwd());
|
|
$truncatedFileName = substr(realpath($_SERVER['SCRIPT_FILENAME']),strlen($cwd));
|
|
$matches = [];
|
|
$configDirForMatch1 = preg_quote($configDir,'/');
|
|
$configDirForMatch2 = preg_quote(str_replace('/','\\',$configDir),'/');
|
|
if (preg_match("/^(.*)(?:$configDirForMatch1|$configDirForMatch2)(.*)index.php$/",$_SERVER['SCRIPT_FILENAME'],$matches)){
|
|
$formFileName = str_replace('\\','/',$matches[2]);
|
|
$data['FROM_SCRIPT_FILENAME'] = $this->formatStringWithLeadingSlash($formFileName,false);
|
|
}
|
|
}
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* extract rootPath from ScriptName
|
|
* @param array &$data
|
|
* @param string $configDir
|
|
* @return self
|
|
*/
|
|
protected function extractRootPathFromScriptNameIfNeeded(array &$data, string $configDir): self
|
|
{
|
|
if ($data['continue'] && !$data['rootPathFound']){
|
|
// use SCRIPT_NAME; e.g. /subfolder/index.php
|
|
$matches = [];
|
|
$wantedPage = empty($data['FROM_SCRIPT_FILENAME']) ? '' : $data['FROM_SCRIPT_FILENAME'];
|
|
$wantedPageQuoted = preg_quote($wantedPage,'/');
|
|
if (preg_match("/^(.*){$wantedPageQuoted}\/(?:index.php)?$/",$_SERVER['SCRIPT_NAME'],$matches)){
|
|
$data['rootPath'] = $this->formatStringWithLeadingSlash($matches[1],true);
|
|
$data['rootPathFound'] = true;
|
|
$data['rewriteModeactivated'] = (realpath($_SERVER['SCRIPT_FILENAME']) == realpath(getcwd()."/{$configDir}index.php"));
|
|
}
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* format string with leading '/'
|
|
* @param null|string $rawString
|
|
* @param bool $withLeadingSlash
|
|
* @return string $page
|
|
*/
|
|
protected function formatStringWithLeadingSlash(?string $rawString, bool $withLeadingSlash = false): string
|
|
{
|
|
return $withLeadingSlash
|
|
? (!empty($rawString) ? (substr($rawString,-1) == '/' ? $rawString : $rawString.'/') : '/')
|
|
: (!empty($rawString) ? (substr($rawString,-1) == '/' ? substr($rawString,0,-1) : $rawString) : '');
|
|
}
|
|
|
|
/**
|
|
* define page
|
|
* @param array $data
|
|
* @return $this
|
|
*/
|
|
protected function definePage(array &$data): self
|
|
{
|
|
if ($data['continue']){
|
|
$data['page'] = !empty($data['FROM_QUERY_STRING'])
|
|
? $data['FROM_QUERY_STRING']
|
|
: (
|
|
!empty($data['FROM_SCRIPT_NAME'])
|
|
? $data['FROM_SCRIPT_NAME']
|
|
: (
|
|
!empty($data['FROM_SCRIPT_FILENAME'])
|
|
? $data['FROM_SCRIPT_FILENAME']
|
|
: 'index'
|
|
)
|
|
);
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* set SERVER QUERY_STRING
|
|
* @param array $data
|
|
* @param string $configDir
|
|
* @param Pico $pico
|
|
* @return $this
|
|
*/
|
|
protected function setUrl(array $data, string $configDir, Pico $pico): self
|
|
{
|
|
$bfserver = $_SERVER;
|
|
// SCRIPT_NAME
|
|
$rootPath = (empty($data['rootPath']) || !is_string($data['rootPath'])) ? '/' : $this->formatStringWithLeadingSlash($data['rootPath'],true);
|
|
$_SERVER['SCRIPT_NAME'] = $rootPath.($data['rewriteModeactivated']?'':$configDir).'index.php';
|
|
|
|
$_SERVER['PHP_SELF'] = $_SERVER['SCRIPT_NAME'].($_SERVER['PATH_INFO'] ?? '');
|
|
$_SERVER['DOCUMENT_URI'] = $_SERVER['PHP_SELF'];
|
|
|
|
// QUERY_STRING
|
|
$qString = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
|
|
if (substr($qString,0,strlen($data['page'])+1)==$data['page'].'&'){
|
|
$qString = substr($qString,strlen($data['page'])+1);
|
|
} elseif ($qString == $data['page']){
|
|
$qString = '';
|
|
}
|
|
if (!empty($data['page'])){
|
|
$qString = $data['page'].(empty($qString) ? '' : "&$qString");
|
|
}
|
|
$_SERVER['QUERY_STRING'] = $qString;
|
|
$_SERVER['REQUEST_URI'] = $_SERVER['DOCUMENT_URI'].(empty($_SERVER['QUERY_STRING'])?'':"?{$_SERVER['QUERY_STRING']}");
|
|
|
|
//SCRIPT_FILENAME
|
|
$cwd = getcwd();
|
|
$fn = str_replace('\\','/',realpath($cwd.'/'.$configDir.'index.php'));
|
|
$_SERVER['SCRIPT_FILENAME'] = $fn;
|
|
|
|
// config
|
|
$baseUrl = $this->getBaseUrl($rootPath,$configDir,$pico);
|
|
$config = $pico->getConfig();
|
|
$config['rewrite_url'] = true;
|
|
$config['configDir'] = $this->formatStringWithLeadingSlash($configDir,false);
|
|
$config['content_dir'] = $this->formatStringWithLeadingSlash($configDir,false);
|
|
$config['themes_url'] = "$baseUrl$rootPath".self::THEMES_PATH;
|
|
$config['plugins_url'] = "$baseUrl$rootPath".self::PLUGINS_PATH;
|
|
$pico->setConfig($config);
|
|
|
|
$server = $_SERVER;
|
|
$dataJSON = json_encode(compact(['data','configDir','qString','cwd','fn','config','server','bfserver']));
|
|
// echo <<<HTML
|
|
// <script>
|
|
// console.log($dataJSON)
|
|
// </script>
|
|
// HTML;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* test if $_SERVER[$key] end by $wantedValue
|
|
* @param string $wantedValue
|
|
* @param string $key
|
|
* @return bool
|
|
*/
|
|
protected function isServerEndedBy(string $wantedValue, string $key): bool
|
|
{
|
|
return (!empty($_SERVER[$key]) && is_string($_SERVER[$key]) && substr($_SERVER[$key],-strlen($wantedValue)) == $wantedValue);
|
|
}
|
|
|
|
/**
|
|
* generate Base Url
|
|
* @param string $rootPath
|
|
* @param string $configDir
|
|
* @param Pico $pico
|
|
* @return string
|
|
*/
|
|
protected function getBaseUrl(string $rootPath,string $configDir, Pico $pico): string
|
|
{
|
|
$baseUrl = $this->formatStringWithLeadingSlash($pico->getBaseUrl(),true);
|
|
|
|
if (substr($baseUrl,-strlen($rootPath.$configDir)) == $rootPath.$configDir){
|
|
$baseUrl = substr($baseUrl,0,-strlen($rootPath.$configDir));
|
|
}
|
|
return $this->formatStringWithLeadingSlash($baseUrl,false);
|
|
}
|
|
} |