diff --git a/App.php b/App.php index f6dedbe..684adb6 100644 --- a/App.php +++ b/App.php @@ -141,64 +141,244 @@ class App */ protected function update_SERVERIfNeeded(Pico $pico, string $configDir) { - $requestUrl = null; - $config = [ - 'rewrite_url' => false, - 'themes_url' => self::THEMES_PATH, - 'plugins_url' => self::PLUGINS_PATH, + $data = [ + 'FROM_QUERY_STRING' => '', + 'FROM_SCRIPT_NAME' => '', + 'FROM_SCRIPT_FILENAME' => '', + 'rootPath' => '/', + 'rootPathFound' => false, + 'rewriteModeactivated' => false, + 'page' => 'index', + 'continue' => true ]; - $nbLevels = 0; + $this + ->extractRequestUrlFormQueryString($data) + ->extractRequestUrlFromScriptFileName($data,$configDir) + ->extractRequestUrlFromScriptName($data,$configDir) + ->extractRootPathFromScriptNameIfNeeded($data,$configDir) + ->definePage($data) + ->setUrl($data, $configDir, $pico); + } - // use QUERY_STRING; e.g. /pico/?sub/page - $pathComponent = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : ''; - if ($pathComponent) { - $pathComponent = strstr($pathComponent, '&', true) ?: $pathComponent; - if (strpos($pathComponent, '=') === false) { - $requestUrl = trim(rawurldecode($pathComponent), '/'); + /** + * 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; + } - if (isset($_SERVER)){ - if (!empty($requestUrl)){ - $supposedEndUrlForRewrite = $configDir.$requestUrl.'/index.php'; - $supposedEndUrlForNotRewrite = $configDir.'index.php'; - if (!empty($_SERVER['SCRIPT_NAME']) && substr($_SERVER['SCRIPT_NAME'],-strlen($supposedEndUrlForRewrite)) == $supposedEndUrlForRewrite){ - $_SERVER['SCRIPT_NAME'] = str_replace($supposedEndUrlForRewrite,$configDir.'index.php',$_SERVER['SCRIPT_NAME']); - if (!empty($_SERVER['SCRIPT_FILENAME'])){ - $_SERVER['SCRIPT_FILENAME'] = str_replace($supposedEndUrlForRewrite,$configDir.'index.php',$_SERVER['SCRIPT_FILENAME']); - } - if (!empty($_SERVER['REQUEST_URI'])){ - $_SERVER['REQUEST_URI'] = str_replace([$configDir.$requestUrl.'/',$configDir.$requestUrl],$configDir,$_SERVER['REQUEST_URI']); - } - $config['rewrite_url'] = true; - $nbLevels = count(explode('/',$configDir.$requestUrl)); - } elseif (!empty($_SERVER['SCRIPT_NAME']) && substr($_SERVER['SCRIPT_NAME'],-strlen($supposedEndUrlForNotRewrite)) == $supposedEndUrlForNotRewrite) { - $nbLevels = count(explode('/',$configDir)) -1; - } - } elseif (!empty($_SERVER['SCRIPT_NAME']) && is_string($_SERVER['SCRIPT_NAME'])) { + /** + * 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 = []; - $configDirForMatch = preg_quote($configDir,'/'); - if (preg_match("/^(.*)$configDirForMatch(.*)index.php$/",$_SERVER['SCRIPT_NAME'],$matches)){ - $rootPath = $matches[1]; - $requestUrl = !empty($matches[2]) ? (substr($matches[2],-1) == '/' ? substr($matches[2],0,-1) : $matches[2]) : ''; - - if (!empty($requestUrl)){ - $supposedEndUrlForRewrite = $configDir.$requestUrl.'/index.php'; - if (!empty($_SERVER['SCRIPT_FILENAME'])){ - $_SERVER['SCRIPT_FILENAME'] = str_replace($supposedEndUrlForRewrite,$configDir.'index.php',$_SERVER['SCRIPT_FILENAME']); - } - if (!empty($_SERVER['SCRIPT_NAME'])){ - $_SERVER['SCRIPT_NAME'] = str_replace($supposedEndUrlForRewrite,$configDir.'index.php',$_SERVER['SCRIPT_NAME']); - } - } - $nbLevels = count(explode('/',$configDir)) -1; - $config['rewrite_url'] = true; + $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); } } } - $previous = implode('',array_fill(0,$nbLevels,'../')); - $config['themes_url'] = $previous.$config['themes_url']; - $config['plugins_url'] = $previous.$config['plugins_url']; + 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 << + // console.log($dataJSON) + // + // 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); } } \ No newline at end of file diff --git a/tests/AppTest.php b/tests/AppTest.php index 078b193..dafa7c4 100644 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -18,31 +18,32 @@ final class AppTest extends TestCase { /** * register error handlers */ - public function testInit() + public function registerErrorHandler() { - $displayErrors = true; - set_error_handler(function( - int $errno, - string $errstr, - ?string $errfile = null, - ?int $errline = null, - ?array $errcontext = null - ) use (&$displayErrors){ - if ($displayErrors){ - echo "\nError $errstr ($errno)\nIn file $errfile\nLine : $errline\n"; - } - }); - register_shutdown_function(function (){ - $lastErr = error_get_last(); - if (!is_null($lastErr)){ - echo "\nAn error occured\n".json_encode($lastErr)."\n"; - } - }); - $this->assertTrue($displayErrors); + if (empty($GLOBALS['errrHandlerSet'])){ + $GLOBALS['errrHandlerSet'] = "1"; + $displayErrors = true; + set_error_handler(function( + int $errno, + string $errstr, + ?string $errfile = null, + ?int $errline = null, + ?array $errcontext = null + ) use (&$displayErrors){ + if ($displayErrors){ + echo "\nError $errstr ($errno)\nIn file $errfile\nLine : $errline\n"; + } + }); + register_shutdown_function(function (){ + $lastErr = error_get_last(); + if (!is_null($lastErr)){ + echo "\nAn error occured\n".json_encode($lastErr)."\n"; + } + }); + } } /** - * @depends testInit * @dataProvider apiRewriteProvider * @covers App::update_SERVERIfNeeded * @param string $rootFolder @@ -67,6 +68,7 @@ final class AppTest extends TestCase { string $waitedPluginsUrl, ): void { + $this->registerErrorHandler(); $this->saveSERVER(); $this->defineServer( true, @@ -85,10 +87,11 @@ final class AppTest extends TestCase { } catch (Throwable $th){ } + $sn = $_SERVER['SCRIPT_NAME']; $cwd = getcwd(); $this->revertSERVER(); $this->assertTrue($thrown,"TestBaseUrlException not found"); - $this->assertEquals($waitedBaseUrl,$foundTh->getBaseUrl(),"Not same baseUrl"); + $this->assertEquals($waitedBaseUrl,$foundTh->getBaseUrl(),"Not same baseUrl ($sn)"); $this->assertEquals("$cwd/vendor/picocms/plugins/",$foundTh->getPluginDir(),"Not same pluginDir"); $this->assertEquals("$cwd/vendor/picocms/themes/",$foundTh->getThemeDir(),"Not same themeDir"); $this->assertEquals("$cwd/",$foundTh->getRootDir(),"Not same rootDir"); @@ -126,10 +129,6 @@ final class AppTest extends TestCase { $this->prepareAPage($data,'/sea','sites/default','sub/index'); $this->prepareAPage($data,'/sea','sites/default','theme'); - // echo "\n"; - // foreach($data as $name => $line){ - // echo "$name => ".implode(',',$line)."\n"; - // } return $data; } @@ -137,53 +136,64 @@ final class AppTest extends TestCase { array &$data, string $baseScriptName, string $rootFolder, - string $pageId + string $waitedPageId ) { $baseUrl = 'http://localhost'.$baseScriptName.'/'; - $isIndex = ($pageId == 'index'); - $isSubIndex = (substr($pageId,-strlen('/index')) == '/index'); - $pageIdInUrl = $isIndex ? '' : ($isSubIndex ? substr($pageId,0,-strlen('/index')) : $pageId) ; - $queriesStrings = $isIndex ? [['q'=>'','s'=>'','f'=>'']] : [ - ['q'=>$pageIdInUrl,'s'=>'','f'=>''], - ['q'=>'','s'=>$pageIdInUrl,'f'=>''], - ['q'=>'','s'=>$pageIdInUrl,'f'=>$pageIdInUrl], - ['q'=>$pageIdInUrl,'s'=>$pageIdInUrl,'f'=>''], - ['q'=>$pageIdInUrl,'s'=>$pageIdInUrl,'f'=>$pageIdInUrl] - ]; - foreach ($queriesStrings as $queryData) { - $queryString = empty($queryData['q']) ? '' :'?'.$queryData['q']; - $scriptNameMiddle = empty($queryData['s']) ? '' : $queryData['s'].'/'; - foreach (['','index.php'] as $endScriptName) { - $formattedQueryString = str_replace('/','%2F',$queryString); - $pageUrl = empty($queryData['s']) ? (empty($queryData['q']) ? '' : $formattedQueryString) : $queryData['s']; - $canRewriteFromRoot = empty($queryData['s']) && empty($queryData['f']); - if ($rootFolder == 'content' && $canRewriteFromRoot){ - $scriptName = "$baseScriptName/$scriptNameMiddle$endScriptName"; - $name = $rootFolder.$scriptName.$queryString.'*'; - $this->prepareData($data,$name,$rootFolder,'index.php',$scriptName,$queryData['q'],$baseUrl,$pageId,$pageUrl); + + // tests + $isIndex = ($waitedPageId == 'index'); + $isSubIndex = (substr($waitedPageId,-strlen('/index')) == '/index'); + + // intermediate variables + $pageIdInUrl = $isIndex ? '' : ($isSubIndex ? substr($waitedPageId,0,-strlen('/index')) : $waitedPageId) ; + $waitedPageEndUrl = $pageIdInUrl; + + foreach([false,true] as $viaQueryString){ + $queryString = $viaQueryString ? $pageIdInUrl : ''; + $queryStringWithQuestion = ($viaQueryString && !empty($pageIdInUrl)) ? "?$pageIdInUrl" : ''; + $formattedQueryString = str_replace('/','%2F',$pageIdInUrl); + + foreach (['','index.php'] as $endShortScriptName) { + + // $filePath from getcwd + + if ($rootFolder == 'content' && $viaQueryString){ + // test also from base + $filePath = 'index.php'; + $shortScriptName = "$baseScriptName/$endShortScriptName"; + $name = $rootFolder.$shortScriptName.$queryStringWithQuestion.'*'; + $waitedBaseUrl = "$baseUrl/content/"; + $this->prepareData($baseUrl,$data,$name,$rootFolder,$filePath,$shortScriptName,$queryString,$waitedBaseUrl,$waitedPageId,$waitedPageEndUrl); } - foreach ([false,true] as $withRewrite) { - if ($withRewrite){ - $scriptName = "$baseScriptName/$scriptNameMiddle$endScriptName"; - $name = $rootFolder.$scriptName.$queryString.' R'; - $waitedUrl = $baseUrl; - } else { - $scriptName = "$baseScriptName/$rootFolder/$scriptNameMiddle$endScriptName"; - $name = $rootFolder.$scriptName.$queryString; - $waitedUrl = $baseUrl.$rootFolder.'/'; + + foreach([false,true] as $viaScriptName){ + $folderInScriptName = ($viaScriptName && !empty($pageIdInUrl)) ? "$pageIdInUrl/" : ''; + $folderInScriptFileName = ($viaScriptName && !empty($pageIdInUrl)) ? "/$pageIdInUrl" :'' ; + + foreach ([false,true] as $withRewrite) { + if ($viaQueryString || $endShortScriptName){ + $filePath = "$rootFolder$folderInScriptFileName/index.php"; + $shortScriptName = $withRewrite + ? "$baseScriptName/$folderInScriptName$endShortScriptName" + : "$baseScriptName/$rootFolder/$folderInScriptName$endShortScriptName"; + + $waitedBaseUrl = $withRewrite + ? $baseUrl + : "$baseUrl$rootFolder/"; + + $name = $rootFolder.$shortScriptName.$queryStringWithQuestion.($withRewrite ? ' R': ''); + $this->prepareData($baseUrl,$data,$name,$rootFolder,$filePath,$shortScriptName,$queryString,$waitedBaseUrl,$waitedPageId,$waitedPageEndUrl); + } } - if ($canRewriteFromRoot){ - $this->prepareData($data,$name."*",$rootFolder,"index.php",$scriptName,$queryData['q'],$waitedUrl,$pageId,$pageUrl); - } - $formattedRootMiddle = empty($pageIdInUrl) ? '' : "/$pageIdInUrl"; - $this->prepareData($data,$name,$rootFolder,"$rootFolder$formattedRootMiddle/index.php",$scriptName,$queryData['q'],$waitedUrl,$pageId,$pageUrl); } } + } } protected function prepareData( + string $baseUrl, array &$data, string $name, string $rootFolder, @@ -196,13 +206,8 @@ final class AppTest extends TestCase { ) { $waitedPageUrl = $waitedBaseUrl.$waitedPageEndUrl; - $nbLevels = in_array($shortScriptName,['/','/index.php']) ? 0 : count(explode('/',$rootFolder)); - $prefix = implode('/',array_fill(0,$nbLevels,'..')); - if (!empty($prefix)){ - $prefix .= '/'; - } - $waitedThemesUrl = $waitedBaseUrl.$prefix.'vendor/picocms/themes'; - $waitedPluginsUrl = $waitedBaseUrl.$prefix.'vendor/picocms/plugins'; + $waitedThemesUrl = $baseUrl.'vendor/picocms/themes'; + $waitedPluginsUrl = $baseUrl.'vendor/picocms/plugins'; $data[$name] = compact([ 'rootFolder', 'filePath', @@ -294,9 +299,10 @@ final class AppTest extends TestCase { $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; // forced $_SERVER['REMOTE_PORT'] = '80'; // forced $_SERVER['SERVER_ADDR'] = '127.0.0.1'; // forced - $_SERVER['SERVER_ADDR'] = '80'; // forced + $_SERVER['SERVER_PORT'] = '80'; // forced $_SERVER['SERVER_NAME'] = 'localhost'; // forced $_SERVER['HTTPS'] = null; // forced + $_SERVER['REQUEST_TIME'] = time(); } }