content = new YellowContent($this);
$this->media = new YellowMedia($this);
$this->system = new YellowSystem($this);
$this->language = new YellowLanguage($this);
$this->user = new YellowUser($this);
$this->extension = new YellowExtension($this);
$this->lookup = new YellowLookup($this);
$this->toolbox = new YellowToolbox($this);
$this->page = new YellowPage($this);
$this->checkRequirements();
$this->system->setDefault("sitename", "Localhost");
$this->system->setDefault("author", "Datenstrom");
$this->system->setDefault("email", "webmaster");
$this->system->setDefault("language", "en");
$this->system->setDefault("layout", "default");
$this->system->setDefault("theme", "default");
$this->system->setDefault("parser", "markdown");
$this->system->setDefault("status", "public");
$this->system->setDefault("coreServerUrl", "auto");
$this->system->setDefault("coreTimezone", "UTC");
$this->system->setDefault("coreContentExtension", ".md");
$this->system->setDefault("coreContentDefaultFile", "page.md");
$this->system->setDefault("coreContentErrorFile", "page-error-(.*).md");
$this->system->setDefault("coreLanguageFile", "yellow-language.ini");
$this->system->setDefault("coreUserFile", "yellow-user.ini");
$this->system->setDefault("coreWebsiteFile", "yellow-website.log");
$this->system->setDefault("coreMediaLocation", "/media/");
$this->system->setDefault("coreDownloadLocation", "/media/downloads/");
$this->system->setDefault("coreImageLocation", "/media/images/");
$this->system->setDefault("coreThumbnailLocation", "/media/thumbnails/");
$this->system->setDefault("coreExtensionLocation", "/media/extensions/");
$this->system->setDefault("coreThemeLocation", "/media/themes/");
$this->system->setDefault("coreMultiLanguageMode", "0");
$this->system->setDefault("coreDebugMode", "0");
}
public function __destruct() {
$this->shutdown();
}
// Check requirements
public function checkRequirements() {
if (!version_compare(PHP_VERSION, "7.0", ">=")) $this->exitFatalError("Datenstrom Yellow requires PHP 7.0 or higher!");
if (!extension_loaded("curl")) $this->exitFatalError("Datenstrom Yellow requires PHP curl extension!");
if (!extension_loaded("gd")) $this->exitFatalError("Datenstrom Yellow requires PHP gd extension!");
if (!extension_loaded("mbstring")) $this->exitFatalError("Datenstrom Yellow requires PHP mbstring extension!");
if (!extension_loaded("zip")) $this->exitFatalError("Datenstrom Yellow requires PHP zip extension!");
mb_internal_encoding("UTF-8");
}
// Handle initialisation
public function load() {
$this->system->load("system/extensions/yellow-system.ini");
$this->system->set("coreSystemFile", "yellow-system.ini");
$this->system->set("coreContentDirectory", "content/");
$this->system->set("coreMediaDirectory", $this->lookup->findMediaDirectory("coreMediaLocation"));
$this->system->set("coreSystemDirectory", "system/");
$this->system->set("coreCacheDirectory", "system/cache/");
$this->system->set("coreExtensionDirectory", "system/extensions/");
$this->system->set("coreLayoutDirectory", "system/layouts/");
$this->system->set("coreThemeDirectory", "system/themes/");
$this->system->set("coreTrashDirectory", "system/trash/");
list($pathInstall, $pathRoot, $pathHome) = $this->lookup->findFileSystemInformation();
$this->system->set("coreServerInstallDirectory", $pathInstall);
$this->system->set("coreServerRootDirectory", $pathRoot);
$this->system->set("coreServerHomeDirectory", $pathHome);
register_shutdown_function(array($this, "processFatalError"));
if ($this->system->get("coreDebugMode")>=1) {
ini_set("display_errors", 1);
error_reporting(E_ALL);
}
date_default_timezone_set($this->system->get("coreTimezone"));
$this->extension->load($this->system->get("coreExtensionDirectory"));
$this->language->load($this->system->get("coreExtensionDirectory").$this->system->get("coreLanguageFile"));
$this->user->load($this->system->get("coreExtensionDirectory").$this->system->get("coreUserFile"));
$this->startup();
}
// Handle request from web browser
public function request() {
$statusCode = 0;
$this->toolbox->timerStart($time);
ob_start();
list($scheme, $address, $base, $location, $fileName) = $this->lookup->getRequestInformation();
$this->page->setRequestInformation($scheme, $address, $base, $location, $fileName, true);
foreach ($this->extension->data as $key=>$value) {
if (method_exists($value["object"], "onRequest")) {
$this->lookup->requestHandler = $key;
$statusCode = $value["object"]->onRequest($scheme, $address, $base, $location, $fileName);
if ($statusCode!=0) break;
}
}
if ($statusCode==0) {
$this->lookup->requestHandler = "core";
$statusCode = $this->processRequest($scheme, $address, $base, $location, $fileName, true);
}
if ($this->page->isError()) $statusCode = $this->processRequestError();
ob_end_flush();
$this->toolbox->timerStop($time);
if ($this->system->get("coreDebugMode")>=1 && ($this->lookup->isContentFile($fileName) || $this->page->isError())) {
echo "YellowCore::request status:$statusCode time:$time ms
\n";
}
return $statusCode;
}
// Process request
public function processRequest($scheme, $address, $base, $location, $fileName, $cacheable) {
$statusCode = 0;
if (is_readable($fileName)) {
if ($this->lookup->isRequestCleanUrl($location)) {
$location = $location.$this->toolbox->getLocationArgumentsCleanUrl();
$location = $this->lookup->normaliseUrl($scheme, $address, $base, $location);
$statusCode = $this->sendStatus(303, $location);
}
} else {
if ($this->lookup->isRedirectLocation($location)) {
$location = $this->lookup->getRedirectLocation($location);
$location = $this->lookup->normaliseUrl($scheme, $address, $base, $location);
$statusCode = $this->sendStatus(301, $location);
}
}
if ($statusCode==0) {
if ($this->lookup->isContentFile($fileName)) {
$statusCode = $this->sendPage($scheme, $address, $base, $location, $fileName, $cacheable, true);
} elseif (!is_string_empty($fileName)) {
$statusCode = $this->sendFile(200, $fileName, $cacheable);
}
if (!is_readable($fileName)) $this->page->error(404);
}
if ($this->system->get("coreDebugMode")>=1 && ($this->lookup->isContentFile($fileName) || $this->page->isError())) {
echo "YellowCore::processRequest file:$fileName
\n";
}
return $statusCode;
}
// Process request with error
public function processRequestError() {
ob_clean();
$statusCode = $this->sendPage($this->page->scheme, $this->page->address, $this->page->base,
$this->page->location, $this->page->fileName, false, false);
if ($this->system->get("coreDebugMode")>=1) echo "YellowCore::processRequestError file:".$this->page->fileName."
\n";
return $statusCode;
}
// Process fatal runtime error
public function processFatalError() {
$error = error_get_last();
if (!is_null($error) && isset($error["type"]) && ($error["type"]==E_ERROR || $error["type"]==E_PARSE)) {
$fileNameAbsolute = isset($error["file"]) ? $error["file"] : "";
$fileName = substru($fileNameAbsolute, strlenu($this->system->get("coreServerInstallDirectory")));
$this->toolbox->log("error", "Can't parse file '$fileName'!");
$this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted(500));
$troubleshooting = PHP_SAPI!="cli" ?
"toolbox->getTroubleshootingUrl()."\">See troubleshooting." : "See ".$this->toolbox->getTroubleshootingUrl();
echo "
\nDatenstrom Yellow stopped with fatal error. Activate the debug mode for more information. $troubleshooting\n";
}
}
// Show error message and terminate immediately
public function exitFatalError($errorMessage = "") {
$this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted(500));
$troubleshooting = PHP_SAPI!="cli" ?
"toolbox->getTroubleshootingUrl()."\">See troubleshooting." : "See ".$this->toolbox->getTroubleshootingUrl();
echo "$errorMessage $troubleshooting\n";
exit(1);
}
// Send page response
public function sendPage($scheme, $address, $base, $location, $fileName, $cacheable, $showSource) {
$rawData = $showSource ? $this->toolbox->readFile($fileName) : $this->page->getRawDataError();
$statusCode = max($this->page->statusCode, 200);
$errorMessage = $this->page->errorMessage;
$this->page = new YellowPage($this);
$this->page->setRequestInformation($scheme, $address, $base, $location, $fileName, $cacheable);
$this->page->parseMeta($rawData, $statusCode, $errorMessage);
$this->language->set($this->page->get("language"));
$this->page->parseContent();
$this->page->parsePage();
$statusCode = $this->sendData($this->page->statusCode, $this->page->headerData, $this->page->outputData);
if ($this->system->get("coreDebugMode")>=1) {
foreach ($this->page->headerData as $key=>$value) {
echo "YellowCore::sendPage $key: $value
\n";
}
$language = $this->page->get("language");
$layout = $this->page->get("layout");
$theme = $this->page->get("theme");
$parser = $this->page->get("parser");
echo "YellowCore::sendPage language:$language layout:$layout theme:$theme parser:$parser
\n";
}
return $statusCode;
}
// Send data response
public function sendData($statusCode, $headerData, $outputData) {
$lastModifiedFormatted = isset($headerData["Last-Modified"]) ? $headerData["Last-Modified"] : "";
if ($statusCode==200 && !isset($headerData["Cache-Control"]) && $this->toolbox->isNotModified($lastModifiedFormatted)) {
$statusCode = 304;
$this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted($statusCode));
} else {
$this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted($statusCode));
foreach ($headerData as $key=>$value) {
$this->toolbox->sendHttpHeader("$key: $value");
}
if (!is_null($outputData)) echo $outputData;
}
return $statusCode;
}
// Send file response
public function sendFile($statusCode, $fileName, $cacheable) {
$lastModifiedFormatted = $this->toolbox->getHttpDateFormatted($this->toolbox->getFileModified($fileName));
if ($statusCode==200 && $cacheable && $this->toolbox->isNotModified($lastModifiedFormatted)) {
$statusCode = 304;
$this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted($statusCode));
} else {
$this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted($statusCode));
if (!$cacheable) $this->toolbox->sendHttpHeader("Cache-Control: no-cache, no-store");
$this->toolbox->sendHttpHeader("Content-Type: ".$this->toolbox->getMimeContentType($fileName));
$this->toolbox->sendHttpHeader("Last-Modified: ".$lastModifiedFormatted);
echo $this->toolbox->readFile($fileName);
}
return $statusCode;
}
// Send status response
public function sendStatus($statusCode, $location = "") {
if (!is_string_empty($location)) $this->page->status($statusCode, $location);
$this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted($statusCode));
foreach ($this->page->headerData as $key=>$value) {
$this->toolbox->sendHttpHeader("$key: $value");
}
return $statusCode;
}
// Handle command from command line
public function command($line = "") {
$statusCode = 0;
$this->toolbox->timerStart($time);
list($command, $text) = $this->lookup->getCommandInformation($line);
foreach ($this->extension->data as $key=>$value) {
if (method_exists($value["object"], "onCommand")) {
$this->lookup->commandHandler = $key;
$statusCode = $value["object"]->onCommand($command, $text);
if ($statusCode!=0) break;
}
}
if ($statusCode==0 && is_string_empty($command)) {
$lines = array();
foreach ($this->extension->data as $key=>$value) {
if (method_exists($value["object"], "onCommandHelp")) {
$this->lookup->commandHandler = $key;
$output = $value["object"]->onCommandHelp();
$lines = array_merge($lines, is_array($output) ? $output : array($output));
}
}
usort($lines, "strnatcasecmp");
$this->showCommandHelp($lines);
$statusCode = 200;
}
if ($statusCode==0) {
$this->lookup->commandHandler = "core";
$statusCode = 400;
echo "Yellow $command: Command not found\n";
}
$this->toolbox->timerStop($time);
if ($this->system->get("coreDebugMode")>=1) {
echo "YellowCore::command status:$statusCode time:$time ms
\n";
}
return $statusCode<400 ? 0 : 1;
}
// Show command help
public function showCommandHelp($lines) {
echo "Datenstrom Yellow is for people who make small websites. https://datenstrom.se/yellow/\n";
$lineCounter = 0;
foreach ($lines as $line) {
echo(++$lineCounter>1 ? " " : "Syntax: ")."php yellow.php $line\n";
}
}
// Handle startup
public function startup() {
if (isset($this->extension->data)) {
foreach ($this->extension->data as $key=>$value) {
if (method_exists($value["object"], "onStartup")) $value["object"]->onStartup();
}
}
}
// Handle shutdown
public function shutdown() {
if (isset($this->extension->data)) {
foreach ($this->extension->data as $key=>$value) {
if (method_exists($value["object"], "onShutdown")) $value["object"]->onShutdown();
}
}
}
// Include layout
public function layout($name, $arguments = null) {
$this->lookup->layoutArguments = func_get_args();
$this->page->includeLayout($name);
}
// Return layout arguments
public function getLayoutArguments($sizeMin = 9) {
return array_pad($this->lookup->layoutArguments, $sizeMin, null);
}
}
class YellowContent {
public $yellow; // access to API
public $pages; // scanned pages
public function __construct($yellow) {
$this->yellow = $yellow;
$this->pages = array();
}
// Scan file system on demand
public function scanLocation($location) {
if (!isset($this->pages[$location])) {
$this->pages[$location] = array();
$scheme = $this->yellow->page->scheme;
$address = $this->yellow->page->address;
$base = $this->yellow->page->base;
if (is_string_empty($location)) {
$rootLocations = $this->yellow->lookup->findContentRootLocations();
foreach ($rootLocations as $rootLocation=>$rootFileName) {
$page = new YellowPage($this->yellow);
$page->setRequestInformation($scheme, $address, $base, $rootLocation, $rootFileName, false);
$page->parseMeta("");
array_push($this->pages[$location], $page);
}
} else {
if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowContent::scanLocation location:$location
\n";
$fileNames = $this->yellow->lookup->findChildrenFromContentLocation($location);
foreach ($fileNames as $fileName) {
$page = new YellowPage($this->yellow);
$page->setRequestInformation($scheme, $address, $base,
$this->yellow->lookup->findContentLocationFromFile($fileName), $fileName, false);
$page->parseMeta($this->yellow->toolbox->readFile($fileName, 4096));
if (strlenb($page->rawData)<4096) $page->statusCode = 200;
array_push($this->pages[$location], $page);
}
}
}
return $this->pages[$location];
}
// Return page from, null if not found
public function find($location, $absoluteLocation = false) {
$found = false;
if ($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
foreach ($this->scanLocation($this->getParentLocation($location)) as $page) {
if ($page->location==$location) {
if (!$this->yellow->lookup->isRootLocation($page->location)) {
$found = true;
break;
}
}
}
return $found ? $page : null;
}
// Return page collection with all pages
public function index($showInvisible = false, $multiLanguage = false, $levelMax = 0) {
$rootLocation = $multiLanguage ? "" : $this->getRootLocation($this->yellow->page->location);
return $this->getChildrenRecursive($rootLocation, $showInvisible, $levelMax);
}
// Return page collection with top-level navigation
public function top($showInvisible = false, $showOnePager = true) {
$rootLocation = $this->getRootLocation($this->yellow->page->location);
$pages = $this->getChildren($rootLocation, $showInvisible);
if (count($pages)==1 && $showOnePager) {
$scheme = $this->yellow->page->scheme;
$address = $this->yellow->page->address;
$base = $this->yellow->page->base;
$one = ($pages->offsetGet(0)->location!=$this->yellow->page->location) ? $pages->offsetGet(0) : $this->yellow->page;
preg_match_all("/(.*?)<\/h\d>/i", $one->getContentHtml(), $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
if ($match[1]==2) {
$page = new YellowPage($this->yellow);
$page->setRequestInformation($scheme, $address, $base, $one->location."#".$match[2], $one->fileName, false);
$page->parseMeta("---\nTitle: $match[3]\n---\n");
$pages->append($page);
}
}
}
return $pages;
}
// Return page collection with path ancestry
public function path($location, $absoluteLocation = false) {
$pages = new YellowPageCollection($this->yellow);
if ($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
if ($page = $this->find($location)) {
$pages->prepend($page);
for (; $parent = $page->getParent(); $page=$parent) {
$pages->prepend($parent);
}
$home = $this->find($this->getHomeLocation($page->location));
if ($home && $home->location!=$page->location) $pages->prepend($home);
}
return $pages;
}
// Return page collection with multiple languages
public function multi($location, $absoluteLocation = false, $showInvisible = false) {
$pages = new YellowPageCollection($this->yellow);
if ($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
$locationEnd = substru($location, strlenu($this->getRootLocation($location)) - 4);
foreach ($this->scanLocation("") as $page) {
if ($content = $this->find(substru($page->location, 4).$locationEnd)) {
if ($content->isAvailable() && ($content->isVisible() || $showInvisible)) {
if (!$this->yellow->lookup->isRootLocation($content->location)) $pages->append($content);
}
}
}
return $pages;
}
// Return page collection that's empty
public function clean() {
return new YellowPageCollection($this->yellow);
}
// Return languages in multi language mode
public function getLanguages($showInvisible = false) {
$languages = array();
if ($this->yellow->system->get("coreMultiLanguageMode")) {
foreach ($this->scanLocation("") as $page) {
if ($page->isAvailable() && ($page->isVisible() || $showInvisible)) array_push($languages, $page->get("language"));
}
}
return $languages;
}
// Return child pages
public function getChildren($location, $showInvisible = false) {
$pages = new YellowPageCollection($this->yellow);
foreach ($this->scanLocation($location) as $page) {
if ($page->isAvailable() && ($page->isVisible() || $showInvisible)) {
if (!$this->yellow->lookup->isRootLocation($page->location) && is_readable($page->fileName)) $pages->append($page);
}
}
return $pages;
}
// Return child pages recursively
public function getChildrenRecursive($location, $showInvisible = false, $levelMax = 0) {
--$levelMax;
$pages = new YellowPageCollection($this->yellow);
foreach ($this->scanLocation($location) as $page) {
if ($page->isAvailable() && ($page->isVisible() || $showInvisible)) {
if (!$this->yellow->lookup->isRootLocation($page->location) && is_readable($page->fileName)) $pages->append($page);
}
if (!$this->yellow->lookup->isFileLocation($page->location) && $levelMax!=0) {
$pages->merge($this->getChildrenRecursive($page->location, $showInvisible, $levelMax));
}
}
return $pages;
}
// Return shared pages
public function getShared($location) {
$pages = new YellowPageCollection($this->yellow);
$sharedLocation = $this->getHomeLocation($location)."shared/";
return $pages->merge($this->scanLocation($sharedLocation));
}
// Return root location
public function getRootLocation($location) {
$rootLocation = "root/";
if ($this->yellow->system->get("coreMultiLanguageMode")) {
foreach ($this->scanLocation("") as $page) {
$token = substru($page->location, 4);
if ($token!="/" && substru($location, 0, strlenu($token))==$token) {
$rootLocation = "root$token";
break;
}
}
}
return $rootLocation;
}
// Return home location
public function getHomeLocation($location) {
return substru($this->getRootLocation($location), 4);
}
// Return parent location
public function getParentLocation($location) {
$parentLocation = "";
$token = rtrim(substru($this->getRootLocation($location), 4), "/");
if (preg_match("#^($token.*\/).+?$#", $location, $matches)) {
if ($matches[1]!="$token/" || $this->yellow->lookup->isFileLocation($location)) $parentLocation = $matches[1];
}
if (is_string_empty($parentLocation)) $parentLocation = "root$token/";
return $parentLocation;
}
// Return top-level location
public function getParentTopLocation($location) {
$parentTopLocation = "";
$token = rtrim(substru($this->getRootLocation($location), 4), "/");
if (preg_match("#^($token.+?\/)#", $location, $matches)) $parentTopLocation = $matches[1];
if (is_string_empty($parentTopLocation)) $parentTopLocation = "$token/";
return $parentTopLocation;
}
}
class YellowMedia {
public $yellow; // access to API
public $files; // scanned files
public function __construct($yellow) {
$this->yellow = $yellow;
$this->files = array();
}
// Scan file system on demand
public function scanLocation($location) {
if (!isset($this->files[$location])) {
$this->files[$location] = array();
$scheme = $this->yellow->page->scheme;
$address = $this->yellow->page->address;
$base = $this->yellow->system->get("coreServerBase");
if (is_string_empty($location)) {
$fileNames = array($this->yellow->system->get("coreMediaDirectory"));
} else {
if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowMedia::scanLocation location:$location
\n";
$fileNames = $this->yellow->lookup->findChildrenFromMediaLocation($location);
}
foreach ($fileNames as $fileName) {
$file = new YellowPage($this->yellow);
$file->setRequestInformation($scheme, $address, $base,
$this->yellow->lookup->findMediaLocationFromFile($fileName), $fileName, false);
$file->parseMeta(null);
array_push($this->files[$location], $file);
}
}
return $this->files[$location];
}
// Return page with media file information, null if not found
public function find($location, $absoluteLocation = false) {
$found = false;
if ($absoluteLocation) $location = substru($location, strlenu($this->yellow->system->get("coreServerBase")));
foreach ($this->scanLocation($this->getParentLocation($location)) as $file) {
if ($file->location==$location) {
if ($this->yellow->lookup->isFileLocation($file->location)) {
$found = true;
break;
}
}
}
return $found ? $file : null;
}
// Return page collection with all media files
public function index($showInvisible = false, $multiPass = false, $levelMax = 0) {
return $this->getChildrenRecursive("", $showInvisible, $levelMax);
}
// Return page collection that's empty
public function clean() {
return new YellowPageCollection($this->yellow);
}
// Return child files
public function getChildren($location, $showInvisible = false) {
$files = new YellowPageCollection($this->yellow);
foreach ($this->scanLocation($location) as $file) {
if ($file->isAvailable() && ($file->isVisible() || $showInvisible)) {
if ($this->yellow->lookup->isFileLocation($file->location)) $files->append($file);
}
}
return $files;
}
// Return child files recursively
public function getChildrenRecursive($location, $showInvisible = false, $levelMax = 0) {
--$levelMax;
$files = new YellowPageCollection($this->yellow);
foreach ($this->scanLocation($location) as $file) {
if ($file->isAvailable() && ($file->isVisible() || $showInvisible)) {
if ($this->yellow->lookup->isFileLocation($file->location)) $files->append($file);
}
if (!$this->yellow->lookup->isFileLocation($file->location) && $levelMax!=0) {
$files->merge($this->getChildrenRecursive($file->location, $showInvisible, $levelMax));
}
}
return $files;
}
// Return home location
public function getHomeLocation($location) {
return $this->yellow->system->get("coreMediaLocation");
}
// Return parent location
public function getParentLocation($location) {
$token = rtrim($this->yellow->system->get("coreMediaLocation"), "/");
if (preg_match("#^($token.*\/).+?$#", $location, $matches)) {
if ($matches[1]!="$token/" || $this->yellow->lookup->isFileLocation($location)) $parentLocation = $matches[1];
}
if (is_string_empty($parentLocation)) $parentLocation = "";
return $parentLocation;
}
// Return top-level location
public function getParentTopLocation($location) {
$token = rtrim($this->yellow->system->get("coreMediaLocation"), "/");
if (preg_match("#^($token.+?\/)#", $location, $matches)) $parentTopLocation = $matches[1];
if (is_string_empty($parentTopLocation)) $parentTopLocation = "$token/";
return $parentTopLocation;
}
}
class YellowSystem {
public $yellow; // access to API
public $modified; // system modification date
public $settings; // system settings
public $settingsDefaults; // system settings defaults
public function __construct($yellow) {
$this->yellow = $yellow;
$this->modified = 0;
$this->settings = new YellowArray();
$this->settingsDefaults = new YellowArray();
}
// Load system settings from file
public function load($fileName) {
$this->modified = $this->yellow->toolbox->getFileModified($fileName);
$fileData = $this->yellow->toolbox->readFile($fileName);
$this->settings = $this->yellow->toolbox->getTextSettings($fileData, "");
if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowSystem::load file:$fileName
\n";
if ($this->yellow->system->get("coreDebugMode")>=3) {
foreach ($this->settings as $key=>$value) {
echo "YellowSystem::load ".ucfirst($key).":$value
\n";
}
}
}
// Save system settings to file
public function save($fileName, $settings) {
$this->modified = time();
$settingsNew = new YellowArray();
foreach ($settings as $key=>$value) {
if (!is_string_empty($key) && !is_string_empty($value)) {
$this->set($key, $value);
$settingsNew[$key] = $value;
}
}
$fileData = $this->yellow->toolbox->readFile($fileName);
$fileData = $this->yellow->toolbox->setTextSettings($fileData, "", "", $settingsNew);
return $this->yellow->toolbox->createFile($fileName, $fileData);
}
// Set default system setting
public function setDefault($key, $value) {
$this->settingsDefaults[$key] = $value;
}
// Set default system settings
public function setDefaults($lines) {
foreach ($lines as $line) {
if (preg_match("/^\#/", $line)) continue;
if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
if (!is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
$this->settingsDefaults[$matches[1]] = $matches[2];
}
}
}
}
// Set system setting
public function set($key, $value) {
$this->settings[$key] = $value;
}
// Return system setting
public function get($key) {
if (isset($this->settings[$key])) {
$value = $this->settings[$key];
} else {
$value = isset($this->settingsDefaults[$key]) ? $this->settingsDefaults[$key] : "";
}
return $value;
}
// Return system setting, HTML encoded
public function getHtml($key) {
return htmlspecialchars($this->get($key));
}
// Return different value for system setting
public function getDifferent($key) {
return reset(array_diff($this->getAvailable($key), array($this->get($key))));
}
// Return available values for system setting
public function getAvailable($key) {
$values = array();
$valueDefault = isset($this->settingsDefaults[$key]) ? $this->settingsDefaults[$key] : "";
if ($key=="email") {
foreach ($this->yellow->user->settings as $userKey=>$userValue) {
array_push($values, $userKey);
}
} elseif ($key=="language") {
foreach ($this->yellow->language->settings as $languageKey=>$languageValue) {
array_push($values, $languageKey);
}
} elseif ($key=="layout") {
$path = $this->yellow->system->get("coreLayoutDirectory");
foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.html$/", true, false, false) as $entry) {
array_push($values, lcfirst(substru($entry, 0, -5)));
}
} elseif ($key=="theme") {
$path = $this->yellow->system->get("coreThemeDirectory");
foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.css$/", true, false, false) as $entry) {
array_push($values, lcfirst(substru($entry, 0, -4)));
}
}
return !is_array_empty($values) ? $values : array($valueDefault);
}
public function getValues($key) { return $this->getAvailable($key); } //TODO: remove later, for backwards compatibility
// Return system settings
public function getSettings($filterStart = "", $filterEnd = "") {
$settings = array();
if (is_string_empty($filterStart) && is_string_empty($filterEnd)) {
$settings = array_merge($this->settingsDefaults->getArrayCopy(), $this->settings->getArrayCopy());
} else {
foreach (array_merge($this->settingsDefaults->getArrayCopy(), $this->settings->getArrayCopy()) as $key=>$value) {
if (!is_string_empty($filterStart) && substru($key, 0, strlenu($filterStart))==$filterStart) $settings[$key] = $value;
if (!is_string_empty($filterEnd) && substru($key, -strlenu($filterEnd))==$filterEnd) $settings[$key] = $value;
}
}
return $settings;
}
// Return system settings modification date, Unix time or HTTP format
public function getModified($httpFormat = false) {
return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
}
// Check if system setting exists
public function isExisting($key) {
return isset($this->settings[$key]);
}
}
class YellowLanguage {
public $yellow; // access to API
public $modified; // language modification date
public $settings; // language settings
public $settingsDefaults; // language settings defaults
public $language; // current language
public function __construct($yellow) {
$this->yellow = $yellow;
$this->modified = 0;
$this->settings = new YellowArray();
$this->settingsDefaults = new YellowArray();
$this->language = "";
}
// Load language settings from file
public function load($fileName) {
$this->modified = $this->yellow->toolbox->getFileModified($fileName);
$fileData = $this->yellow->toolbox->readFile($fileName);
$settings = $this->yellow->toolbox->getTextSettings($fileData, "language");
foreach ($settings as $language=>$block) {
if (!isset($this->settings[$language])) $this->settings[$language] = new YellowArray();
foreach ($block as $key=>$value) {
$this->settings[$language][$key] = $value;
}
}
if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowLanguage::load file:$fileName
\n";
foreach ($this->settings->getArrayCopy() as $key=>$value) {
if (!isset($this->settings[$key]["languageDescription"])) {
unset($this->settings[$key]);
}
}
$callback = function ($a, $b) {
return strnatcmp($a["languageDescription"], $b["languageDescription"]);
};
$this->settings->uasort($callback);
}
// Set current language
public function set($language) {
$this->language = $language;
}
// Set default language setting
public function setDefault($key, $value, $language) {
if (!isset($this->settings[$language])) $this->settings[$language] = new YellowArray();
$this->settings[$language][$key] = $value;
$this->settingsDefaults[$key] = $value;
}
// Set default language settings
public function setDefaults($lines) {
$language = "";
foreach ($lines as $line) {
if (preg_match("/^\#/", $line)) continue;
if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
if (lcfirst($matches[1])=="language" && !is_string_empty($matches[2])) {
$language = $matches[2];
if (!isset($this->settings[$language])) $this->settings[$language] = new YellowArray();
}
if (!is_string_empty($language) && !is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
$this->settings[$language][$matches[1]] = $matches[2];
$this->settingsDefaults[$matches[1]] = true;
}
}
}
}
// Set language setting
public function setText($key, $value, $language) {
if (!isset($this->settings[$language])) $this->settings[$language] = new YellowArray();
$this->settings[$language][$key] = $value;
}
// Return language setting
public function getText($key, $language = "") {
if (is_string_empty($language)) $language = $this->language;
return $this->isText($key, $language) ? $this->settings[$language][$key] : "[$key]";
}
// Return language setting, HTML encoded
public function getTextHtml($key, $language = "") {
return htmlspecialchars($this->getText($key, $language));
}
// Return human readable date
public function getDateFormatted($timestamp, $format, $language = "") {
$dateMonthsNominative = preg_split("/\s*,\s*/", $this->getText("coreDateMonthsNominative", $language));
$dateMonthsGenitive = preg_split("/\s*,\s*/", $this->getText("coreDateMonthsGenitive", $language));
$dateWeekdays = preg_split("/\s*,\s*/", $this->getText("coreDateWeekdays", $language));
$monthNominative = $dateMonthsNominative[date("n", $timestamp) - 1];
$monthGenitive = $dateMonthsGenitive[date("n", $timestamp) - 1];
$weekday = $dateWeekdays[date("N", $timestamp) - 1];
$timeZone = $this->yellow->system->get("coreTimezone");
$timeZoneHelper = new DateTime("now", new DateTimeZone($timeZone));
$timeZoneOffset = $timeZoneHelper->getOffset();
$timeZoneAbbreviation = "GMT".($timeZoneOffset<0 ? "-" : "+").abs(intval($timeZoneOffset/3600));
$format = preg_replace("/(?=0 ? "coreDatePast" : "coreDateFuture";
$tokens = preg_split("/\s*,\s*/", $this->getText($key, $language));
if (count($tokens)>=8) {
if ($days<=$daysLimit || $daysLimit==0) {
if ($days==0) {
$output = $tokens[0];
} elseif ($days==1) {
$output = $tokens[1];
} elseif ($days>=2 && $days<=29) {
$output = preg_replace("/@x/i", $days, $tokens[2]);
} elseif ($days>=30 && $days<=59) {
$output = $tokens[3];
} elseif ($days>=60 && $days<=364) {
$output = preg_replace("/@x/i", intval($days/30), $tokens[4]);
} elseif ($days>=365 && $days<=729) {
$output = $tokens[5];
} else {
$output = preg_replace("/@x/i", intval($days/365.25), $tokens[6]);
}
} else {
$output = preg_replace("/@x/i", $this->getDateFormatted($timestamp, $format, $language), $tokens[7]);
}
} else {
$output = "[$key]";
}
return $output;
}
// Return language settings
public function getSettings($filterStart = "", $filterEnd = "", $language = "") {
$settings = array();
if (is_string_empty($language)) $language = $this->language;
if (isset($this->settings[$language])) {
if (is_string_empty($filterStart) && is_string_empty($filterEnd)) {
$settings = $this->settings[$language]->getArrayCopy();
} else {
foreach ($this->settings[$language] as $key=>$value) {
if (!is_string_empty($filterStart) && substru($key, 0, strlenu($filterStart))==$filterStart) $settings[$key] = $value;
if (!is_string_empty($filterEnd) && substru($key, -strlenu($filterEnd))==$filterEnd) $settings[$key] = $value;
}
}
}
return $settings;
}
// Return language settings modification date, Unix time or HTTP format
public function getModified($httpFormat = false) {
return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
}
// Normalise date into known format
public function normaliseDate($text, $language = "") {
if (preg_match("/^\d+\-\d+$/", $text)) {
$output = $this->getDateFormatted(strtotime($text), $this->getText("coreDateFormatShort", $language), $language);
} elseif (preg_match("/^\d+\-\d+\-\d+$/", $text)) {
$output = $this->getDateFormatted(strtotime($text), $this->getText("coreDateFormatMedium", $language), $language);
} elseif (preg_match("/^\d+\-\d+\-\d+ \d+\:\d+$/", $text)) {
$output = $this->getDateFormatted(strtotime($text), $this->getText("coreDateFormatLong", $language), $language);
} else {
$output = $text;
}
return $output;
}
// Check if language setting exists
public function isText($key, $language = "") {
if (is_string_empty($language)) $language = $this->language;
return isset($this->settings[$language]) && isset($this->settings[$language][$key]);
}
// Check if language exists
public function isExisting($language) {
return isset($this->settings[$language]);
}
}
class YellowUser {
public $yellow; // access to API
public $modified; // user modification date
public $settings; // user settings
public $email; // current email
public function __construct($yellow) {
$this->yellow = $yellow;
$this->modified = 0;
$this->settings = new YellowArray();
$this->email = "";
}
// Load user settings from file
public function load($fileName) {
$this->modified = $this->yellow->toolbox->getFileModified($fileName);
$fileData = $this->yellow->toolbox->readFile($fileName);
$this->settings = $this->yellow->toolbox->getTextSettings($fileData, "email");
if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowUser::load file:$fileName
\n";
}
// Save user settings to file
public function save($fileName, $email, $settings) {
$this->modified = time();
$settingsNew = new YellowArray();
$settingsNew["email"] = $email;
foreach ($settings as $key=>$value) {
if (!is_string_empty($key) && !is_string_empty($value)) {
$this->setUser($key, $value, $email);
$settingsNew[$key] = $value;
}
}
$fileData = $this->yellow->toolbox->readFile($fileName);
$fileData = $this->yellow->toolbox->setTextSettings($fileData, "email", $email, $settingsNew);
return $this->yellow->toolbox->createFile($fileName, $fileData);
}
// Remove user settings from file
public function remove($fileName, $email) {
$this->modified = time();
if (isset($this->settings[$email])) unset($this->settings[$email]);
$fileData = $this->yellow->toolbox->readFile($fileName);
$fileData = $this->yellow->toolbox->unsetTextSettings($fileData, "email", $email);
return $this->yellow->toolbox->createFile($fileName, $fileData);
}
// Set current email
public function set($email) {
$this->email = $email;
}
// Set user setting
public function setUser($key, $value, $email) {
if (!isset($this->settings[$email])) $this->settings[$email] = new YellowArray();
$this->settings[$email][$key] = $value;
}
// Return user setting
public function getUser($key, $email = "") {
if (is_string_empty($email)) $email = $this->email;
return isset($this->settings[$email]) && isset($this->settings[$email][$key]) ? $this->settings[$email][$key] : "";
}
// Return user setting, HTML encoded
public function getUserHtml($key, $email = "") {
return htmlspecialchars($this->getUser($key, $email));
}
// Return user settings
public function getSettings($email = "") {
$settings = array();
if (is_string_empty($email)) $email = $this->email;
if (isset($this->settings[$email])) $settings = $this->settings[$email]->getArrayCopy();
return $settings;
}
// Return user settings modification date, Unix time or HTTP format
public function getModified($httpFormat = false) {
return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
}
// Check if user setting exists
public function isUser($key, $email = "") {
if (is_string_empty($email)) $email = $this->email;
return isset($this->settings[$email]) && isset($this->settings[$email][$key]);
}
// Check if user exists
public function isExisting($email) {
return isset($this->settings[$email]);
}
}
class YellowExtension {
public $yellow; // access to API
public $modified; // extension modification date
public $data; // extension data
public function __construct($yellow) {
$this->yellow = $yellow;
$this->modified = 0;
$this->data = array();
}
// Load extensions
public function load($path) {
foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.php$/", true, false) as $entry) {
$this->modified = max($this->modified, filemtime($entry));
require_once($entry);
$name = $this->yellow->lookup->normaliseName(basename($entry), true, true);
$this->register(lcfirst($name), "Yellow".ucfirst($name));
if ($this->yellow->system->get("coreDebugMode")>=3) echo "YellowExtension::load file:$entry
\n";
}
$callback = function ($a, $b) {
return $a["priority"] - $b["priority"];
};
uasort($this->data, $callback);
foreach ($this->data as $key=>$value) {
if (method_exists($this->data[$key]["object"], "onLoad")) $this->data[$key]["object"]->onLoad($this->yellow);
}
}
// Register extension
public function register($key, $class) {
if (!$this->isExisting($key) && class_exists($class)) {
$this->data[$key] = array();
$this->data[$key]["object"] = $class=="YellowCore" ? new stdClass : new $class;
$this->data[$key]["class"] = $class;
$this->data[$key]["version"] = defined("$class::VERSION") ? $class::VERSION : 0;
$this->data[$key]["priority"] = defined("$class::PRIORITY") ? $class::PRIORITY : count($this->data) + 10;
}
}
// Return extension
public function get($key) {
return $this->data[$key]["object"];
}
// Return extensions modification date, Unix time or HTTP format
public function getModified($httpFormat = false) {
return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
}
// Check if extension exists
public function isExisting($key) {
return isset($this->data[$key]);
}
}
class YellowLookup {
public $yellow; // access to API
public $requestHandler; // request handler name
public $commandHandler; // command handler name
public $layoutArguments; // layout arguments
public function __construct($yellow) {
$this->yellow = $yellow;
}
// Return file system information
public function findFileSystemInformation() {
$pathInstall = substru(__DIR__, 0, 1-strlenu($this->yellow->system->get("coreExtensionDirectory")));
$pathBase = $this->yellow->system->get("coreContentDirectory");
$pathRoot = $this->yellow->system->get("coreMultiLanguageMode") ? "default/" : "";
$pathHome = "home/";
if (!is_string_empty($pathRoot)) {
$firstRoot = "";
$token = $root = rtrim($pathRoot, "/");
foreach ($this->yellow->toolbox->getDirectoryEntries($pathBase, "/.*/", true, true, false) as $entry) {
if (is_string_empty($firstRoot)) $firstRoot = $token = $entry;
if ($this->normaliseToken($entry)==$root) {
$token = $entry;
break;
}
}
$pathRoot = $this->normaliseToken($token)."/";
$pathBase .= "$firstRoot/";
}
if (!is_string_empty($pathHome)) {
$firstHome = "";
$token = $home = rtrim($pathHome, "/");
foreach ($this->yellow->toolbox->getDirectoryEntries($pathBase, "/.*/", true, true, false) as $entry) {
if (is_string_empty($firstHome)) $firstHome = $token = $entry;
if ($this->normaliseToken($entry)==$home) {
$token = $entry;
break;
}
}
$pathHome = $this->normaliseToken($token)."/";
}
return array($pathInstall, $pathRoot, $pathHome);
}
// Return content language
public function findContentLanguage($fileName, $languageDefault) {
$language = $languageDefault;
$pathBase = $this->yellow->system->get("coreContentDirectory");
$pathRoot = $this->yellow->system->get("coreServerRootDirectory");
if (!is_string_empty($pathRoot)) {
$fileName = substru($fileName, strlenu($pathBase));
if (preg_match("/^(.+?)\//", $fileName, $matches)) {
$name = $this->normaliseToken($matches[1]);
if (strlenu($name)==2) $language = $name;
}
}
return $language;
}
// Return content root locations
public function findContentRootLocations() {
$rootLocations = array();
$pathBase = $this->yellow->system->get("coreContentDirectory");
$pathRoot = $this->yellow->system->get("coreServerRootDirectory");
if (!is_string_empty($pathRoot)) {
foreach ($this->yellow->toolbox->getDirectoryEntries($pathBase, "/.*/", true, true, false) as $entry) {
$token = $this->normaliseToken($entry)."/";
if ($token==$pathRoot) $token = "";
$rootLocations["root/$token"] = "$pathBase$entry/";
}
} else {
$rootLocations["root/"] = "$pathBase";
}
if ($this->yellow->system->get("coreDebugMode")>=3) {
foreach ($rootLocations as $key=>$key) {
echo "YellowLookup::findContentRootLocations $key -> $value
\n";
}
}
return $rootLocations;
}
// Return content location from file path
public function findContentLocationFromFile($fileName) {
$invalid = false;
$location = "/";
$pathBase = $this->yellow->system->get("coreContentDirectory");
$pathRoot = $this->yellow->system->get("coreServerRootDirectory");
$pathHome = $this->yellow->system->get("coreServerHomeDirectory");
$fileDefault = $this->yellow->system->get("coreContentDefaultFile");
$fileExtension = $this->yellow->system->get("coreContentExtension");
if (substru($fileName, 0, strlenu($pathBase))==$pathBase && mb_check_encoding($fileName, "UTF-8")) {
$fileName = substru($fileName, strlenu($pathBase));
$tokens = explode("/", $fileName);
if (!is_string_empty($pathRoot)) {
$token = $this->normaliseToken($tokens[0])."/";
if ($token!=$pathRoot) $location .= $token;
array_shift($tokens);
}
for ($i=0; $inormaliseToken($tokens[$i])."/";
if ($i || $token!=$pathHome) $location .= $token;
}
$token = $this->normaliseToken($tokens[$i], $fileExtension);
if ($token!=$fileDefault) {
$location .= $this->normaliseToken($tokens[$i], $fileExtension, true);
}
$extension = ($pos = strrposu($fileName, ".")) ? substru($fileName, $pos) : "";
if ($extension!=$fileExtension) $invalid = true;
} else {
$invalid = true;
}
if ($this->yellow->system->get("coreDebugMode")>=2) {
$debug = ($invalid ? "INVALID" : $location)." <- $pathBase$fileName";
echo "YellowLookup::findContentLocationFromFile $debug
\n";
}
return $invalid ? "" : $location;
}
// Return file path from content location
public function findFileFromContentLocation($location, $directory = false) {
$found = $invalid = false;
$path = $this->yellow->system->get("coreContentDirectory");
$pathRoot = $this->yellow->system->get("coreServerRootDirectory");
$pathHome = $this->yellow->system->get("coreServerHomeDirectory");
$fileDefault = $this->yellow->system->get("coreContentDefaultFile");
$fileExtension = $this->yellow->system->get("coreContentExtension");
$tokens = explode("/", $location);
if ($this->isRootLocation($location)) {
if (!is_string_empty($pathRoot)) {
$token = (count($tokens)>2) ? $tokens[1] : rtrim($pathRoot, "/");
$path .= $this->findFileDirectory($path, $token, "", true, true, $found, $invalid);
}
} else {
if (!is_string_empty($pathRoot)) {
if (count($tokens)>2) {
if ($this->normaliseToken($tokens[1])==$this->normaliseToken(rtrim($pathRoot, "/"))) $invalid = true;
$path .= $this->findFileDirectory($path, $tokens[1], "", true, false, $found, $invalid);
if ($found) array_shift($tokens);
}
if (!$found) {
$path .= $this->findFileDirectory($path, rtrim($pathRoot, "/"), "", true, true, $found, $invalid);
}
}
if (count($tokens)>2) {
if ($this->normaliseToken($tokens[1])==$this->normaliseToken(rtrim($pathHome, "/"))) $invalid = true;
for ($i=1; $ifindFileDirectory($path, $tokens[$i], "", true, true, $found, $invalid);
}
} else {
$i = 1;
$tokens[0] = rtrim($pathHome, "/");
$path .= $this->findFileDirectory($path, $tokens[0], "", true, true, $found, $invalid);
}
if (!$directory) {
if (!is_string_empty($tokens[$i])) {
$token = $tokens[$i].$fileExtension;
if ($token==$fileDefault) $invalid = true;
$path .= $this->findFileDirectory($path, $token, $fileExtension, false, true, $found, $invalid);
} else {
$path .= $this->findFileDefault($path, $fileDefault, $fileExtension, false);
}
if ($this->yellow->system->get("coreDebugMode")>=2) {
$debug = "$location -> ".($invalid ? "INVALID" : $path);
echo "YellowLookup::findFileFromContentLocation $debug
\n";
}
}
}
return $invalid ? "" : $path;
}
// Return children from content location
public function findChildrenFromContentLocation($location) {
$fileNames = array();
if (!$this->isFileLocation($location)) {
$path = $this->findFileFromContentLocation($location, true);
$fileDefault = $this->yellow->system->get("coreContentDefaultFile");
$fileExtension = $this->yellow->system->get("coreContentExtension");
foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, true, false) as $entry) {
$token = $this->findFileDefault($path.$entry, $fileDefault, $fileExtension, false);
array_push($fileNames, $path.$entry."/".$token);
}
if (!$this->isRootLocation($location)) {
$regex = "/^.*\\".$fileExtension."$/";
foreach ($this->yellow->toolbox->getDirectoryEntries($path, $regex, true, false, false) as $entry) {
if ($this->normaliseToken($entry, $fileExtension)==$fileDefault) continue;
array_push($fileNames, $path.$entry);
}
}
}
return $fileNames;
}
// Return media location from file path
public function findMediaLocationFromFile($fileName) {
$location = "";
$extensionDirectoryLength = strlenu($this->yellow->system->get("coreExtensionDirectory"));
$themeDirectoryLength = strlenu($this->yellow->system->get("coreThemeDirectory"));
$mediaDirectoryLength = strlenu($this->yellow->system->get("coreMediaDirectory"));
if (substru($fileName, 0, $extensionDirectoryLength)==$this->yellow->system->get("coreExtensionDirectory")) {
if ($this->isSafeFile($fileName)) {
$location = $this->yellow->system->get("coreExtensionLocation").substru($fileName, $extensionDirectoryLength);
}
} elseif (substru($fileName, 0, $themeDirectoryLength)==$this->yellow->system->get("coreThemeDirectory")) {
if ($this->isSafeFile($fileName)) {
$location = $this->yellow->system->get("coreThemeLocation").substru($fileName, $themeDirectoryLength);
}
} elseif (substru($fileName, 0, $mediaDirectoryLength)==$this->yellow->system->get("coreMediaDirectory")) {
$location = "/".$fileName;
}
return $location;
}
// Return file path from media location
public function findFileFromMediaLocation($location) {
$fileName = "";
$extensionLocationLength = strlenu($this->yellow->system->get("coreExtensionLocation"));
$themeLocationLength = strlenu($this->yellow->system->get("coreThemeLocation"));
$mediaLocationLength = strlenu($this->yellow->system->get("coreMediaLocation"));
if (substru($location, 0, $extensionLocationLength)==$this->yellow->system->get("coreExtensionLocation")) {
if ($this->isSafeFile($location)) {
$fileName = $this->yellow->system->get("coreExtensionDirectory").substru($location, $extensionLocationLength);
}
} elseif (substru($location, 0, $themeLocationLength)==$this->yellow->system->get("coreThemeLocation")) {
if ($this->isSafeFile($location)) {
$fileName = $this->yellow->system->get("coreThemeDirectory").substru($location, $themeLocationLength);
}
} elseif (substru($location, 0, $mediaLocationLength)==$this->yellow->system->get("coreMediaLocation")) {
$fileName = substru($location, 1);
}
return $fileName;
}
// Return children from media location
public function findChildrenFromMediaLocation($location) {
$fileNames = array();
if (!$this->isFileLocation($location)) {
$path = $this->findFileFromMediaLocation($location);
foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, true, true) as $entry) {
array_push($fileNames, $entry."/");
}
foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, false, true) as $entry) {
array_push($fileNames, $entry);
}
}
return $fileNames;
}
// Return media directory from a system setting
public function findMediaDirectory($key) {
return substru($key, -8, 8)=="Location" ? $this->findFileFromMediaLocation($this->yellow->system->get($key)) : "";
}
// Return file or directory that matches token
public function findFileDirectory($path, $token, $fileExtension, $directory, $default, &$found, &$invalid) {
if ($this->normaliseToken($token, $fileExtension)!=$token) $invalid = true;
if (!$invalid) {
$regex = "/^[\d\-\_\.]*".str_replace("-", ".", $token)."$/";
foreach ($this->yellow->toolbox->getDirectoryEntries($path, $regex, false, $directory, false) as $entry) {
if ($this->normaliseToken($entry, $fileExtension)==$token) {
$token = $entry;
$found = true;
break;
}
}
}
if ($directory) $token .= "/";
return ($default || $found) ? $token : "";
}
// Return default file in directory
public function findFileDefault($path, $fileDefault, $fileExtension, $includePath = true) {
$token = $fileDefault;
if (!is_file($path."/".$fileDefault)) {
$regex = "/^[\d\-\_\.]*($fileDefault)$/";
foreach ($this->yellow->toolbox->getDirectoryEntries($path, $regex, true, false, false) as $entry) {
if ($this->normaliseToken($entry, $fileExtension)==$fileDefault) {
$token = $entry;
break;
}
}
}
return $includePath ? "$path/$token" : $token;
}
// Normalise file/directory token
public function normaliseToken($text, $fileExtension = "", $removeExtension = false) {
if (!is_string_empty($fileExtension)) $text = ($pos = strrposu($text, ".")) ? substru($text, 0, $pos) : $text;
if (preg_match("/^[\d\-\_\.]+(.*)$/", $text, $matches) && !is_string_empty($matches[1])) $text = $matches[1];
return preg_replace("/[^\pL\d\-\_]/u", "-", $text).($removeExtension ? "" : $fileExtension);
}
// Normalise name
public function normaliseName($text, $removePrefix = false, $removeExtension = false, $filterStrict = false) {
if ($removeExtension) $text = ($pos = strrposu($text, ".")) ? substru($text, 0, $pos) : $text;
if ($removePrefix && preg_match("/^[\d\-\_\.]+(.*)$/", $text, $matches) && !is_string_empty($matches[1])) $text = $matches[1];
if ($filterStrict) $text = strtoloweru($text);
return preg_replace("/[^\pL\d\-\_]/u", "-", $text);
}
// Normalise prefix
public function normalisePrefix($text) {
$prefix = "";
if (preg_match("/^([\d\-\_\.]*)(.*)$/", $text, $matches)) $prefix = $matches[1];
if (!is_string_empty($prefix) && !preg_match("/[\-\_\.]$/", $prefix)) $prefix .= "-";
return $prefix;
}
// Normalise elements and attributes in HTML/SVG data
public function normaliseData($text, $type = "html", $filterStrict = true) {
$output = "";
$elementsHtml = array(
"a", "abbr", "acronym", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big", "blink", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data", "datalist", "dd", "decorator", "del", "details", "dfn", "dir", "div", "dl", "dt", "element", "em", "fieldset", "figcaption", "figure", "font", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "iframe", "image", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "marquee", "menu", "menuitem", "meta", "meter", "nav", "nobr", "ol", "optgroup", "option", "output", "p", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "section", "select", "shadow", "small", "source", "spacer", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr");
$elementsSvg = array(
"svg", "altglyph", "altglyphdef", "altglyphitem", "animatecolor", "animatemotion", "animatetransform", "circle", "clippath", "defs", "desc", "ellipse", "feblend", "fecolormatrix", "fecomponenttransfer", "fecomposite", "feconvolvematrix", "fediffuselighting", "fedisplacementmap", "fedistantlight", "feflood", "fefunca", "fefuncb", "fefuncg", "fefuncr", "fegaussianblur", "femerge", "femergenode", "femorphology", "feoffset", "fepointlight", "fespecularlighting", "fespotlight", "fetile", "feturbulence", "filter", "font", "g", "glyph", "glyphref", "hkern", "image", "line", "lineargradient", "marker", "mask", "metadata", "mpath", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "stop", "switch", "symbol", "text", "textpath", "title", "tref", "tspan", "use", "view", "vkern");
$attributesHtml = array(
"accept", "action", "align", "allow", "allowfullscreen", "alt", "autocomplete", "autoplay", "background", "bgcolor", "border", "cellpadding", "cellspacing", "charset", "checked", "cite", "class", "clear", "color", "cols", "colspan", "content", "contenteditable", "controls", "coords", "crossorigin", "datetime", "default", "dir", "disabled", "download", "enctype", "face", "for", "frameborder", "headers", "height", "hidden", "high", "href", "hreflang", "id", "integrity", "ismap", "label", "lang", "list", "loading", "loop", "low", "max", "maxlength", "media", "method", "min", "multiple", "muted", "name", "noshade", "novalidate", "nowrap", "open", "optimum", "pattern", "placeholder", "poster", "prefix", "preload", "property", "pubdate", "radiogroup", "readonly", "rel", "required", "rev", "reversed", "role", "rows", "rowspan", "sandbox", "spellcheck", "scope", "selected", "shape", "size", "sizes", "span", "srclang", "start", "src", "srcset", "step", "style", "summary", "tabindex", "target", "title", "type", "usemap", "valign", "value", "width", "xmlns");
$attributesSvg = array(
"accent-height", "accumulate", "additivive", "alignment-baseline", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "datenstrom", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dur", "edgemode", "elevation", "end", "fill", "fill-opacity", "fill-rule", "filter", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "specularconstant", "specularexponent", "spreadmethod", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "tabindex", "targetx", "targety", "transform", "text-anchor", "text-decoration", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xlink:href", "xml:id", "xml:space", "xmlns", "y", "y1", "y2", "z", "zoomandpan");
$attributesAllowEmptyString = array("alt", "download", "sandbox", "value");
$elementsSafe = $elementsHtml;
$attributesSafe = $attributesHtml;
if ($type=="svg") {
$elementsSafe = array_merge($elementsHtml, $elementsSvg);
$attributesSafe = array_merge($attributesHtml, $attributesSvg);
}
$offsetBytes = 0;
while (true) {
$elementFound = preg_match("/<(\/?)([\!\?\w]+)(.*?)(\/?)>/s", $text, $matches, PREG_OFFSET_CAPTURE, $offsetBytes);
$elementBefore = $elementFound ? substrb($text, $offsetBytes, $matches[0][1] - $offsetBytes) : substrb($text, $offsetBytes);
$elementStart = $elementFound ? $matches[1][0] : "";
$elementName = $elementFound ? $matches[2][0]: "";
$elementMiddle = $elementFound ? $matches[3][0]: "";
$elementEnd = $elementFound ? $matches[4][0]: "";
$output .= $elementBefore;
if (substrb($elementName, 0, 1)=="!") {
$output .= "<$elementName$elementMiddle>";
} elseif (in_array(strtolower($elementName), $elementsSafe)) {
$elementAttributes = $this->getTextAttributes($elementMiddle, $attributesAllowEmptyString);
foreach ($elementAttributes as $key=>$value) {
if (!in_array(strtolower($key), $attributesSafe) && !preg_match("/^(aria|data)-/i", $key)) {
unset($elementAttributes[$key]);
}
}
if ($filterStrict) {
$href = isset($elementAttributes["href"]) ? $elementAttributes["href"] : "";
if (preg_match("/^\w+:/", $href) && !$this->isSafeUrl($href)) {
$elementAttributes["href"] = "error-xss-filter";
}
$href = isset($elementAttributes["xlink:href"]) ? $elementAttributes["xlink:href"] : "";
if (preg_match("/^\w+:/", $href) && !$this->isSafeUrl($href)) {
$elementAttributes["xlink:href"] = "error-xss-filter";
}
}
$output .= "<$elementStart$elementName";
foreach ($elementAttributes as $key=>$value) $output .= " $key=\"$value\"";
if (!is_string_empty($elementEnd)) $output .= " ";
$output .= "$elementEnd>";
}
if (!$elementFound) break;
$offsetBytes = $matches[0][1] + strlenb($matches[0][0]);
}
return $output;
}
// Normalise fields in MIME headers
public function normaliseHeaders($input, $type = "mime", $filterStrict = true) {
$output = "";
if ($type=="mime") {
$keysMixedEncoding = array("To", "From", "Reply-To", "Cc", "Bcc");
foreach ($input as $key=>$value) {
$key = ucwords(preg_replace("/[^a-zA-Z\-]/u", "-", $key), "-");
if (in_array($key, $keysMixedEncoding)) {
$text = "$key: ";
foreach (preg_split("/\s*,\s*/", $value) as $email) {
if (!preg_match("/^(.*?)(\s*)<(.*?)>$/", $email, $matches)) {
$matches[1] = $matches[2] = "";
$matches[3] = $email;
}
if ($filterStrict && !preg_match("/[\w\+\-\.\@]+/", $matches[3])) {
$matches[3] = "error-mail-filter";
}
if (substru($text, -2, 2)!=": ") $text .= ",\r\n ";
$text = $this->getMimeHeader($text, $matches[1]);
$text = $this->getMimeHeader($text, "$matches[2]<$matches[3]>", false);
}
$text .= "\r\n";
} else {
$text = $this->getMimeHeader("$key: ", $value)."\r\n";
}
$output .= $text;
}
}
return $output;
}
// Normalise array, make keys with same upper/lower case
public function normaliseArray($input) {
$array = array();
foreach ($input as $key=>$value) {
if (is_string_empty($key) || is_string_empty($value)) continue;
$keySearch = strtoloweru($key);
foreach ($array as $keyNew=>$valueNew) {
if (strtoloweru($keyNew)==$keySearch) {
$key = $keyNew;
break;
}
}
if (!isset($array[$key])) $array[$key] = 0;
$array[$key] += $value;
}
return $array;
}
// Normalise relative path tokens
public function normalisePath($text) {
$textFiltered = "";
$textLength = strlenb($text);
for ($pos=0; $pos<$textLength; ++$pos) {
if ($text[$pos]=="." && ($pos==0 || $text[$pos-1]=="/")) {
while ($text[$pos]==".") ++$pos;
if ($text[$pos]=="/") ++$pos;
--$pos;
continue;
}
$textFiltered .= $text[$pos];
}
return $textFiltered;
}
// Normalise text lines, convert line endings
public function normaliseLines($text, $endOfLine = "lf") {
if ($endOfLine=="lf") {
$text = preg_replace("/\R/u", "\n", $text);
} else {
$text = preg_replace("/\R/u", "\r\n", $text);
}
return $text;
}
// Normalise text into UTF-8 NFC
public function normaliseUnicode($text) {
if (PHP_OS=="Darwin" && !mb_check_encoding($text, "ASCII")) {
$utf8nfc = preg_match("//u", $text) && !preg_match("/[^\\x00-\\x{2FF}]/u", $text);
if (!$utf8nfc) $text = iconv("UTF-8-MAC", "UTF-8", $text);
}
return $text;
}
// Normalise location, make absolute location
public function normaliseLocation($location, $pageLocation, $filterStrict = true) {
if (!preg_match("/^\w+:/", trim(html_entity_decode($location, ENT_QUOTES, "UTF-8")))) {
$pageBase = $this->yellow->page->base;
$mediaBase = $this->yellow->system->get("coreServerBase").$this->yellow->system->get("coreMediaLocation");
if (!preg_match("/^\#/", $location)) {
if (!preg_match("/^\//", $location)) {
$location = $this->getDirectoryLocation($pageBase.$pageLocation).$location;
} elseif (!preg_match("#^($pageBase|$mediaBase)#", $location)) {
$location = $pageBase.$location;
}
}
$location = str_replace("/./", "/", $location);
$location = str_replace(":", $this->yellow->toolbox->getLocationArgumentsSeparator(), $location);
} else {
if ($filterStrict && !$this->isSafeUrl($location)) $location = "error-xss-filter";
}
return $location;
}
// Normalise location arguments
public function normaliseArguments($text, $appendSlash = true, $filterStrict = true) {
if ($appendSlash) $text .= "/";
if ($filterStrict) $text = str_replace(" ", "-", strtoloweru($text));
$text = str_replace(":", $this->yellow->toolbox->getLocationArgumentsSeparator(), $text);
return str_replace(array("%2F","%3A","%3D"), array("/",":","="), rawurlencode($text));
}
// Normalise URL, make absolute URL
public function normaliseUrl($scheme, $address, $base, $location, $filterStrict = true) {
if (!preg_match("/^\w+:/", $location)) {
$url = "$scheme://$address$base$location";
} else {
if ($filterStrict && !$this->isSafeUrl($location)) $location = "error-xss-filter";
$url = $location;
}
return $url;
}
// Return URL information
public function getUrlInformation($url) {
$scheme = $address = $base = "";
if (preg_match("#^(\w+)://([^/]+)(.*)$#", rtrim($url, "/"), $matches)) {
$scheme = $matches[1];
$address = $matches[2];
$base = $matches[3];
}
return array($scheme, $address, $base);
}
// Return request information
public function getRequestInformation($scheme = "", $address = "", $base = "") {
if (is_string_empty($scheme) && is_string_empty($address) && is_string_empty($base)) {
$url = $this->yellow->system->get("coreServerUrl");
if ($url=="auto" || $this->isCommandLine()) $url = $this->yellow->toolbox->detectServerUrl();
list($scheme, $address, $base) = $this->getUrlInformation($url);
$this->yellow->system->set("coreServerScheme", $scheme);
$this->yellow->system->set("coreServerAddress", $address);
$this->yellow->system->set("coreServerBase", $base);
if ($this->yellow->system->get("coreDebugMode")>=3) {
echo "YellowLookup::getRequestInformation $scheme://$address$base
\n";
}
}
$location = substru($this->yellow->toolbox->detectServerLocation(), strlenu($base));
$fileName = "";
if (is_string_empty($fileName)) $fileName = $this->findFileFromMediaLocation($location);
if (is_string_empty($fileName)) $fileName = $this->findFileFromContentLocation($location);
return array($scheme, $address, $base, $location, $fileName);
}
// Return command information
public function getCommandInformation($line = "") {
if (is_string_empty($line)) {
$line = $this->yellow->toolbox->getTextString(array_slice($this->yellow->toolbox->getServer("argv"), 1));
if ($this->yellow->system->get("coreDebugMode")>=3) {
echo "YellowLookup::getCommandInformation $line
\n";
}
}
return $this->yellow->toolbox->getTextList($line, " ", 2);
}
// Return request handler
public function getRequestHandler() {
return $this->requestHandler;
}
// Return command handler
public function getCommandHandler() {
return $this->commandHandler;
}
// Return attributes from text
public function getTextAttributes($text, $attributesAllowEmptyString) {
$tokens = array();
$posStart = $posQuote = 0;
$textLength = strlenb($text);
for ($pos=0; $pos<$textLength; ++$pos) {
if ($text[$pos]==" " && !$posQuote) {
if ($pos>$posStart) array_push($tokens, substrb($text, $posStart, $pos-$posStart));
$posStart = $pos+1;
}
if ($text[$pos]=="=" && !$posQuote) {
if ($pos>$posStart) array_push($tokens, substrb($text, $posStart, $pos-$posStart));
array_push($tokens, "=");
$posStart = $pos+1;
}
if ($text[$pos]=="\"") {
if ($posQuote) {
if ($pos>$posQuote) array_push($tokens, substrb($text, $posQuote+1, $pos-$posQuote-1));
$posQuote = 0;
$posStart = $pos+1;
} else {
if ($pos==$posStart) $posQuote = $pos;
}
}
}
if ($pos>$posStart && !$posQuote) {
array_push($tokens, substrb($text, $posStart, $pos-$posStart));
}
$attributes = array();
for ($i=0; $i0;--$pos) {
if ($field[$fieldPos+$pos]==" ") {
$fragment = substrb($field, $fieldPos, $pos);
$bytesHandled = $pos+1;
break;
}
}
if ($pos==0) $encode = true;
}
if ($encode) {
while (true) {
$fragment = substrb($field, $fieldPos, $bytesHandled);
if (!is_string_empty($fragment)) $fragment = "=?UTF-8?B?".base64_encode($fragment)."?=";
if ($bytesAvailable>=strlenb($fragment) || $bytesHandled==0) break;
--$bytesHandled;
}
}
$text .= $fragment."\r\n ";
$fieldPos += $bytesHandled;
} else {
$text .= $fragment;
break;
}
}
} else {
$textPos = strlenb($text)-(($pos = strrposb($text, "\n")) ? $pos+1 : 0);
$bytesAvailable = max(0, 78-$textPos);
if ($bytesAvailableisFileLocation($location)) {
$location = "$location/";
} else {
$languageDefault = $this->yellow->system->get("language");
$language = $this->yellow->toolbox->detectBrowserLanguage($this->yellow->content->getLanguages(), $languageDefault);
$location = "/$language/";
}
return $location;
}
// Check if clean URL is requested
public function isRequestCleanUrl($location) {
return isset($_REQUEST["clean-url"]) && substru($location, -1, 1)=="/";
}
// Check if location is specifying root
public function isRootLocation($location) {
return substru($location, 0, 1)!="/";
}
// Check if location is specifying file or directory
public function isFileLocation($location) {
return substru($location, -1, 1)!="/";
}
// Check if location can be redirected into directory
public function isRedirectLocation($location) {
$redirect = false;
if ($this->isFileLocation($location)) {
$redirect = is_dir($this->findFileFromContentLocation("$location/", true));
} elseif ($location=="/") {
$redirect = $this->yellow->system->get("coreMultiLanguageMode");
}
return $redirect;
}
// Check if location contains nested directories
public function isNestedLocation($location, $fileName, $checkHomeLocation = false) {
$nested = false;
if (!$checkHomeLocation || $location==$this->yellow->content->getHomeLocation($location)) {
$path = dirname($fileName);
if (!is_array_empty($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, true, false))) $nested = true;
}
return $nested;
}
// Check if location is available
public function isAvailableLocation($location, $fileName) {
$available = true;
$pathBase = $this->yellow->system->get("coreContentDirectory");
if (substru($fileName, 0, strlenu($pathBase))==$pathBase) {
$sharedLocation = $this->yellow->content->getHomeLocation($location)."shared/";
if (substru($location, 0, strlenu($sharedLocation))==$sharedLocation) $available = false;
}
return $available;
}
// Check if location is within current HTTP request
public function isActiveLocation($location, $currentLocation) {
if ($this->isFileLocation($location)) {
$active = $currentLocation==$location;
} else {
if ($location==$this->yellow->content->getHomeLocation($location)) {
$active = $this->getDirectoryLocation($currentLocation)==$location;
} else {
$active = substru($currentLocation, 0, strlenu($location))==$location;
}
}
return $active;
}
// Check if URL is a well-known URL scheme
public function isSafeUrl($url) {
return preg_match("/^(http|https|ftp|mailto|tel):/", $url);
}
// Check if file is a well-known file type
public function isSafeFile($fileName) {
return preg_match("/\.(css|gif|ico|js|jpg|png|svg|woff|woff2)$/", $fileName);
}
// Check if file is valid
public function isValidFile($fileName) {
$contentDirectoryLength = strlenu($this->yellow->system->get("coreContentDirectory"));
$mediaDirectoryLength = strlenu($this->yellow->system->get("coreMediaDirectory"));
$systemDirectoryLength = strlenu($this->yellow->system->get("coreSystemDirectory"));
return strposu($fileName, "/")===false ||
substru($fileName, 0, $contentDirectoryLength)==$this->yellow->system->get("coreContentDirectory") ||
substru($fileName, 0, $mediaDirectoryLength)==$this->yellow->system->get("coreMediaDirectory") ||
substru($fileName, 0, $systemDirectoryLength)==$this->yellow->system->get("coreSystemDirectory");
}
// Check if content file
public function isContentFile($fileName) {
$contentDirectoryLength = strlenu($this->yellow->system->get("coreContentDirectory"));
return substru($fileName, 0, $contentDirectoryLength)==$this->yellow->system->get("coreContentDirectory");
}
// Check if media file
public function isMediaFile($fileName) {
$mediaDirectoryLength = strlenu($this->yellow->system->get("coreMediaDirectory"));
return substru($fileName, 0, $mediaDirectoryLength)==$this->yellow->system->get("coreMediaDirectory");
}
// Check if system file
public function isSystemFile($fileName) {
$systemDirectoryLength = strlenu($this->yellow->system->get("coreSystemDirectory"));
return substru($fileName, 0, $systemDirectoryLength)==$this->yellow->system->get("coreSystemDirectory");
}
// Check if running at command line
public function isCommandLine() {
return isset($this->commandHandler);
}
}
class YellowToolbox {
public $yellow; // access to API
public function __construct($yellow) {
$this->yellow = $yellow;
}
// Return browser cookie from from current HTTP request
public function getCookie($key) {
return isset($_COOKIE[$key]) ? $_COOKIE[$key] : "";
}
// Return server argument from current HTTP request
public function getServer($key) {
return isset($_SERVER[$key]) ? $_SERVER[$key] : "";
}
// Return location arguments from current HTTP request
public function getLocationArguments() {
return $this->getServer("LOCATION_ARGUMENTS");
}
// Return location arguments from current HTTP request, modify existing arguments
public function getLocationArgumentsNew($key, $value) {
$locationArguments = "";
$found = false;
$separator = $this->getLocationArgumentsSeparator();
foreach (explode("/", $this->getServer("LOCATION_ARGUMENTS")) as $token) {
if (preg_match("/^(.*?)$separator(.*)$/", $token, $matches)) {
if ($matches[1]==$key) {
$matches[2] = $value;
$found = true;
}
if (!is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
if (!is_string_empty($locationArguments)) $locationArguments .= "/";
$locationArguments .= "$matches[1]:$matches[2]";
}
}
}
if (!$found && !is_string_empty($key) && !is_string_empty($value)) {
if (!is_string_empty($locationArguments)) $locationArguments .= "/";
$locationArguments .= "$key:$value";
}
if (!is_string_empty($locationArguments)) {
$locationArguments = $this->yellow->lookup->normaliseArguments($locationArguments, false, false);
if (!$this->isLocationArgumentsPagination($locationArguments)) $locationArguments .= "/";
}
return $locationArguments;
}
// Return location arguments from current HTTP request, convert form parameters
public function getLocationArgumentsCleanUrl() {
$locationArguments = "";
foreach (array_merge($_GET, $_POST) as $key=>$value) {
if (!is_string_empty($key) && !is_string_empty($value)) {
if (!is_string_empty($locationArguments)) $locationArguments .= "/";
$key = str_replace(array("/", ":", "="), array("\x1c", "\x1d", "\x1e"), $key);
$value = str_replace(array("/", ":", "="), array("\x1c", "\x1d", "\x1e"), $value);
$locationArguments .= "$key:$value";
}
}
if (!is_string_empty($locationArguments)) {
$locationArguments = $this->yellow->lookup->normaliseArguments($locationArguments, false, false);
if (!$this->isLocationArgumentsPagination($locationArguments)) $locationArguments .= "/";
}
return $locationArguments;
}
// Return location arguments separator
public function getLocationArgumentsSeparator() {
return (strtoupperu(substru(PHP_OS, 0, 3))!="WIN") ? ":" : "=";
}
// Return human readable HTTP date
public function getHttpDateFormatted($timestamp) {
return gmdate("D, d M Y H:i:s", $timestamp)." GMT";
}
// Return human readable HTTP server status
public function getHttpStatusFormatted($statusCode, $shortFormat = false) {
switch ($statusCode) {
case 0: $text = "No data"; break;
case 200: $text = "OK"; break;
case 301: $text = "Moved permanently"; break;
case 302: $text = "Moved temporarily"; break;
case 303: $text = "Reload please"; break;
case 304: $text = "Not modified"; break;
case 400: $text = "Bad request"; break;
case 403: $text = "Forbidden"; break;
case 404: $text = "Not found"; break;
case 420: $text = "Not public"; break;
case 430: $text = "Login failed"; break;
case 434: $text = "Can create"; break;
case 435: $text = "Can restore"; break;
case 450: $text = "Update error"; break;
case 500: $text = "Server error"; break;
case 503: $text = "Service unavailable"; break;
default: $text = "Error $statusCode";
}
$serverProtocol = $this->getServer("SERVER_PROTOCOL");
if (!preg_match("/^HTTP\//", $serverProtocol)) $serverProtocol = "HTTP/1.1";
return $shortFormat ? $text : "$serverProtocol $statusCode $text";
}
// Return MIME content type
public function getMimeContentType($fileName) {
$contentType = "";
$contentTypes = array(
"css" => "text/css",
"gif" => "image/gif",
"html" => "text/html; charset=utf-8",
"ico" => "image/x-icon",
"js" => "application/javascript",
"json" => "application/json",
"jpg" => "image/jpeg",
"md" => "text/markdown",
"png" => "image/png",
"svg" => "image/svg+xml",
"txt" => "text/plain",
"woff" => "application/font-woff",
"woff2" => "application/font-woff2",
"xml" => "text/xml; charset=utf-8");
$fileType = $this->getFileType($fileName);
if (is_string_empty($fileType)) {
$contentType = $contentTypes["html"];
} elseif (array_key_exists($fileType, $contentTypes)) {
$contentType = $contentTypes[$fileType];
}
return $contentType;
}
// Send HTTP header
public function sendHttpHeader($text) {
if (!headers_sent()) header($text);
}
// Return files and directories
public function getDirectoryEntries($path, $regex = "/.*/", $sort = true, $directories = true, $includePath = true) {
$entries = array();
$directoryHandle = @opendir($path);
if ($directoryHandle) {
$path = rtrim($path, "/");
while (($entry = readdir($directoryHandle))!==false) {
if (substru($entry, 0, 1)==".") continue;
$entry = $this->yellow->lookup->normaliseUnicode($entry);
if (preg_match($regex, $entry)) {
if ($directories) {
if (is_dir("$path/$entry")) array_push($entries, $includePath ? "$path/$entry" : $entry);
} else {
if (is_file("$path/$entry")) array_push($entries, $includePath ? "$path/$entry" : $entry);
}
}
}
if ($sort) natcasesort($entries);
closedir($directoryHandle);
}
return $entries;
}
// Return files and directories recursively
public function getDirectoryEntriesRecursive($path, $regex = "/.*/", $sort = true, $directories = true, $levelMax = 0) {
--$levelMax;
$entries = $this->getDirectoryEntries($path, $regex, $sort, $directories);
if ($levelMax!=0) {
foreach ($this->getDirectoryEntries($path, "/.*/", $sort, true) as $entry) {
$entries = array_merge($entries, $this->getDirectoryEntriesRecursive($entry, $regex, $sort, $directories, $levelMax));
}
}
return $entries;
}
// Read file, empty string if not found
public function readFile($fileName, $sizeMax = 0) {
$fileData = "";
$fileHandle = @fopen($fileName, "rb");
if ($fileHandle) {
clearstatcache(true, $fileName);
if (flock($fileHandle, LOCK_SH)) {
$fileSize = $sizeMax ? $sizeMax : filesize($fileName);
if ($fileSize) $fileData = fread($fileHandle, $fileSize);
flock($fileHandle, LOCK_UN);
}
fclose($fileHandle);
}
return $fileData;
}
// Create file
public function createFile($fileName, $fileData, $mkdir = false) {
$ok = false;
if ($mkdir) {
$path = dirname($fileName);
if (!is_string_empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
}
$fileHandle = @fopen($fileName, "cb");
if ($fileHandle) {
clearstatcache(true, $fileName);
if (flock($fileHandle, LOCK_EX)) {
ftruncate($fileHandle, 0);
fwrite($fileHandle, $fileData);
flock($fileHandle, LOCK_UN);
}
fclose($fileHandle);
$ok = true;
}
return $ok;
}
// Append file
public function appendFile($fileName, $fileData, $mkdir = false) {
$ok = false;
if ($mkdir) {
$path = dirname($fileName);
if (!is_string_empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
}
$fileHandle = @fopen($fileName, "ab");
if ($fileHandle) {
clearstatcache(true, $fileName);
if (flock($fileHandle, LOCK_EX)) {
fwrite($fileHandle, $fileData);
flock($fileHandle, LOCK_UN);
}
fclose($fileHandle);
$ok = true;
}
return $ok;
}
// Copy file
public function copyFile($fileNameSource, $fileNameDestination, $mkdir = false) {
clearstatcache();
if ($mkdir) {
$path = dirname($fileNameDestination);
if (!is_string_empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
}
return @copy($fileNameSource, $fileNameDestination);
}
// Rename file
public function renameFile($fileNameSource, $fileNameDestination, $mkdir = false) {
clearstatcache();
if ($mkdir) {
$path = dirname($fileNameDestination);
if (!is_string_empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
}
return @rename($fileNameSource, $fileNameDestination);
}
// Rename directory
public function renameDirectory($pathSource, $pathDestination, $mkdir = false) {
return $pathSource==$pathDestination || $this->renameFile($pathSource, $pathDestination, $mkdir);
}
// Delete file
public function deleteFile($fileName, $pathTrash = "") {
clearstatcache();
if (is_string_empty($pathTrash)) {
$ok = @unlink($fileName);
} else {
if (!is_dir($pathTrash)) @mkdir($pathTrash, 0777, true);
$fileNameDestination = $pathTrash;
$fileNameDestination .= pathinfo($fileName, PATHINFO_FILENAME);
$fileNameDestination .= "-".str_replace(array(" ", ":"), "-", date("Y-m-d H:i:s"));
$fileNameDestination .= ".".pathinfo($fileName, PATHINFO_EXTENSION);
$ok = @rename($fileName, $fileNameDestination);
}
return $ok;
}
// Delete directory
public function deleteDirectory($path, $pathTrash = "") {
clearstatcache();
if (is_string_empty($pathTrash)) {
$iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($files as $file) {
if ($file->getType()=="dir") {
@rmdir($file->getPathname());
} else {
@unlink($file->getPathname());
}
}
$ok = @rmdir($path);
} else {
if (!is_dir($pathTrash)) @mkdir($pathTrash, 0777, true);
$pathDestination = $pathTrash;
$pathDestination .= basename($path);
$pathDestination .= "-".str_replace(array(" ", ":"), "-", date("Y-m-d H:i:s"));
$ok = @rename($path, $pathDestination);
}
return $ok;
}
// Set file/directory modification date, Unix time
public function modifyFile($fileName, $modified) {
clearstatcache(true, $fileName);
return @touch($fileName, $modified);
}
// Return file/directory modification date, Unix time
public function getFileModified($fileName) {
return (is_file($fileName) || is_dir($fileName)) ? filemtime($fileName) : 0;
}
// Return file/directory deletion date, Unix time
public function getFileDeleted($fileName) {
$deleted = 0;
$text = basename($fileName);
$text = ($pos = strrposu($text, ".")) ? substru($text, 0, $pos) : $text;
if (preg_match("#^(.+)-(\d\d\d\d-\d\d-\d\d)-(\d\d)-(\d\d)-(\d\d)$#", $text, $matches)) {
$deleted = strtotime("$matches[2] $matches[3]:$matches[4]:$matches[5]");
}
return $deleted;
}
// Return file type
public function getFileType($fileName) {
return strtoloweru(($pos = strrposu($fileName, ".")) ? substru($fileName, $pos+1) : "");
}
// Return file group
public function getFileGroup($fileName, $path) {
$group = "none";
if (preg_match("#^$path(.+?)\/#", $fileName, $matches)) $group = strtoloweru($matches[1]);
return $group;
}
// Return number of bytes
public function getNumberBytes($text) {
$bytes = intval($text);
switch (strtoupperu(substru($text, -1))) {
case "G": $bytes *= 1024*1024*1024; break;
case "M": $bytes *= 1024*1024; break;
case "K": $bytes *= 1024; break;
}
return $bytes;
}
// Return lines from text, including newline
public function getTextLines($text) {
$lines = preg_split("/\n/", $text);
foreach ($lines as &$line) {
$line = $line."\n";
}
if (is_string_empty($text) || substru($text, -1, 1)=="\n") array_pop($lines);
return $lines;
}
// Return settings from text
function getTextSettings($text, $blockStart) {
$settings = new YellowArray();
if (is_string_empty($blockStart)) {
foreach ($this->getTextLines($text) as $line) {
if (preg_match("/^\#/", $line)) continue;
if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
if (!is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
$settings[$matches[1]] = $matches[2];
}
}
}
} else {
$blockKey = "";
foreach ($this->getTextLines($text) as $line) {
if (preg_match("/^\#/", $line)) continue;
if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
if (lcfirst($matches[1])==$blockStart && !is_string_empty($matches[2])) {
$blockKey = $matches[2];
$settings[$blockKey] = new YellowArray();
}
if (!is_string_empty($blockKey) && !is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
$settings[$blockKey][$matches[1]] = $matches[2];
}
}
}
}
return $settings;
}
// Set settings in text
function setTextSettings($text, $blockStart, $blockKey, $settings) {
$textNew = "";
if (is_string_empty($blockStart)) {
foreach ($this->getTextLines($text) as $line) {
if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
if (!is_string_empty($matches[1]) && isset($settings[$matches[1]])) {
$textNew .= "$matches[1]: ".$settings[$matches[1]]."\n";
unset($settings[$matches[1]]);
continue;
}
}
$textNew .= $line;
}
foreach ($settings as $key=>$value) {
$textNew .= (strposu($key, "/") ? $key : ucfirst($key)).": $value\n";
}
} else {
$scan = false;
$textStart = $textMiddle = $textEnd = "";
foreach ($this->getTextLines($text) as $line) {
if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
if (lcfirst($matches[1])==$blockStart && !is_string_empty($matches[2])) {
$scan = lcfirst($matches[2])==lcfirst($blockKey);
}
}
if (!$scan && is_string_empty($textMiddle)) {
$textStart .= $line;
} elseif ($scan) {
$textMiddle .= $line;
} else {
$textEnd .= $line;
}
}
$textSettings = "";
foreach ($this->getTextLines($textMiddle) as $line) {
if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
if (!is_string_empty($matches[1]) && isset($settings[$matches[1]])) {
$textSettings .= "$matches[1]: ".$settings[$matches[1]]."\n";
unset($settings[$matches[1]]);
continue;
}
$textSettings .= $line;
}
}
foreach ($settings as $key=>$value) {
$textSettings .= (strposu($key, "/") ? $key : ucfirst($key)).": $value\n";
}
if (!is_string_empty($textMiddle)) {
$textMiddle = $textSettings;
if (!is_string_empty($textEnd)) $textMiddle .= "\n";
} else {
if (!is_string_empty($textStart)) $textEnd .= "\n";
$textEnd .= $textSettings;
}
$textNew = $textStart.$textMiddle.$textEnd;
}
return $textNew;
}
// Remove settings from text
function unsetTextSettings($text, $blockStart, $blockKey) {
$textNew = "";
if (!is_string_empty($blockStart)) {
$scan = false;
$textStart = $textMiddle = $textEnd = "";
foreach ($this->getTextLines($text) as $line) {
if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
if (lcfirst($matches[1])==$blockStart && !is_string_empty($matches[2])) {
$scan = lcfirst($matches[2])==lcfirst($blockKey);
}
}
if (!$scan && is_string_empty($textMiddle)) {
$textStart .= $line;
} elseif ($scan) {
$textMiddle .= $line;
} else {
$textEnd .= $line;
}
}
$textNew = rtrim($textStart.$textEnd)."\n";
}
return $textNew;
}
// Return array of specific size from text
public function getTextList($text, $separator, $size) {
$tokens = explode($separator, $text, $size);
return array_pad($tokens, $size, "");
}
// Return array of variable size from text, space separated
public function getTextArguments($text, $optional = "-", $sizeMin = 9) {
$text = preg_replace("/\s+/s", " ", trim($text));
$tokens = str_getcsv($text, " ", "\"");
foreach ($tokens as $key=>$value) {
if (is_null($value) || $value==$optional) $tokens[$key] = "";
}
return array_pad($tokens, $sizeMin, "");
}
// Return text from array, space separated
public function getTextString($tokens, $optional = "-") {
$text = "";
foreach ($tokens as $token) {
if (preg_match("/\s/", $token)) $token = "\"$token\"";
if (is_string_empty($token)) $token = $optional;
if (!is_string_empty($text)) $text .= " ";
$text .= $token;
}
return $text;
}
// Return number of words in text
public function getTextWords($text) {
$text = preg_replace("/([\p{Han}\p{Hiragana}\p{Katakana}]{3})/u", "$1 ", $text);
$text = preg_replace("/(\pL|\p{N})/u", "x", $text);
return str_word_count($text);
}
// Return text truncated at word boundary
public function getTextTruncated($text, $lengthMax) {
if (strlenu($text)>$lengthMax-1) {
$text = substru($text, 0, $lengthMax);
$pos = strrposu($text, " ");
$text = substru($text, 0, $pos ? $pos : $lengthMax-1)."…";
}
return $text;
}
// Create text description, with or without HTML
public function createTextDescription($text, $lengthMax = 0, $removeHtml = true, $endMarker = "", $endMarkerText = "") {
$output = "";
$elementsBlock = array("blockquote", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "li", "ol", "p", "pre", "ul");
$elementsVoid = array("area", "br", "col", "embed", "hr", "img", "input", "param", "source", "wbr");
if ($lengthMax==0) $lengthMax = strlenu($text);
if ($removeHtml) {
$hiddenLevel = 0;
$offsetBytes = 0;
while (true) {
$elementFound = preg_match("/<(\/?)([\!\?\w]+)(.*?)(\/?)>/s", $text, $matches, PREG_OFFSET_CAPTURE, $offsetBytes);
$elementBefore = $elementFound ? substrb($text, $offsetBytes, $matches[0][1] - $offsetBytes) : substrb($text, $offsetBytes);
$elementRawData = isset($matches[0][0]) ? $matches[0][0] : "";
$elementStart = isset($matches[1][0]) ? $matches[1][0] : "";
$elementName = isset($matches[2][0]) ? $matches[2][0] : "";
$elementAttributes = isset($matches[3][0]) ? $matches[3][0] : "";
$elementEnd = isset($matches[4][0]) ? $matches[4][0] : "";
if (!is_string_empty($elementBefore) && !$hiddenLevel) {
$rawText = preg_replace("/\s+/s", " ", html_entity_decode($elementBefore, ENT_QUOTES, "UTF-8"));
if (is_string_empty($elementStart) && in_array(strtolower($elementName), $elementsBlock)) $rawText = rtrim($rawText)." ";
if (substru($rawText, 0, 1)==" " && (is_string_empty($output) || substru($output, -1)==" ")) $rawText = ltrim($rawText);
$output .= $this->getTextTruncated($rawText, $lengthMax);
$lengthMax -= strlenu($rawText);
}
if (!is_string_empty($elementRawData) && $elementRawData==$endMarker) {
$output .= $endMarkerText;
$lengthMax = 0;
}
if ($lengthMax<=0 || !$elementFound) break;
if ($hiddenLevel>0 ||
preg_match("/aria-hidden=\"true\"/i", $elementAttributes) ||
preg_match("/role=\"doc-noteref\"/i", $elementAttributes)) {
if (!is_string_empty($elementName) && is_string_empty($elementEnd) && !in_array(strtolower($elementName), $elementsVoid)) {
if (is_string_empty($elementStart)) {
++$hiddenLevel;
} else {
--$hiddenLevel;
}
}
}
$offsetBytes = $matches[0][1] + strlenb($matches[0][0]);
}
$output = preg_replace("/\s+\…$/s", "…", $output);
} else {
$elementsOpen = array();
$offsetBytes = 0;
while (true) {
$elementFound = preg_match("/&.*?\;|<(\/?)([\!\?\w]+)(.*?)(\/?)>/s", $text, $matches, PREG_OFFSET_CAPTURE, $offsetBytes);
$elementBefore = $elementFound ? substrb($text, $offsetBytes, $matches[0][1] - $offsetBytes) : substrb($text, $offsetBytes);
$elementRawData = isset($matches[0][0]) ? $matches[0][0] : "";
$elementStart = isset($matches[1][0]) ? $matches[1][0] : "";
$elementName = isset($matches[2][0]) ? $matches[2][0] : "";
$elementEnd = isset($matches[4][0]) ? $matches[4][0] : "";
if (!is_string_empty($elementBefore)) {
$output .= $this->getTextTruncated($elementBefore, $lengthMax);
$lengthMax -= strlenu($elementBefore);
}
if (!is_string_empty($elementRawData) && $elementRawData==$endMarker) {
$output .= $endMarkerText;
$lengthMax = 0;
}
if ($lengthMax<=0 || !$elementFound) break;
if (!is_string_empty($elementName) && is_string_empty($elementEnd) && !in_array(strtolower($elementName), $elementsVoid)) {
if (is_string_empty($elementStart)) {
array_push($elementsOpen, $elementName);
} else {
array_pop($elementsOpen);
}
}
$output .= $elementRawData;
if ($elementRawData[0]=="&") --$lengthMax;
$offsetBytes = $matches[0][1] + strlenb($matches[0][0]);
}
$output = preg_replace("/\s+\…$/s", "…", $output);
for ($i=count($elementsOpen)-1; $i>=0; --$i) {
$output .= "".$elementsOpen[$i].">";
}
}
return trim($output);
}
// Create title from text
public function createTextTitle($text) {
if (preg_match("/^.*\/([\pL\d\-\_]+)/u", $text, $matches)) $text = str_replace("-", " ", ucfirst($matches[1]));
return $text;
}
// Create random text for cryptography
public function createSalt($length, $bcryptFormat = false) {
$dataBuffer = $salt = "";
$dataBufferSize = $bcryptFormat ? intval(ceil($length/4) * 3) : intval(ceil($length/2));
if (is_string_empty($dataBuffer) && function_exists("random_bytes")) {
$dataBuffer = @random_bytes($dataBufferSize);
}
if (is_string_empty($dataBuffer) && function_exists("openssl_random_pseudo_bytes")) {
$dataBuffer = @openssl_random_pseudo_bytes($dataBufferSize);
}
if (strlenb($dataBuffer)==$dataBufferSize) {
if ($bcryptFormat) {
$salt = substrb(base64_encode($dataBuffer), 0, $length);
$base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
$bcrypt64Chars = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
$salt = strtr($salt, $base64Chars, $bcrypt64Chars);
} else {
$salt = substrb(bin2hex($dataBuffer), 0, $length);
}
}
return $salt;
}
// Create hash with random salt, bcrypt or sha256
public function createHash($text, $algorithm, $cost = 0) {
$hash = "";
switch ($algorithm) {
case "bcrypt": $prefix = sprintf("$2y$%02d$", $cost);
$salt = $this->createSalt(22, true);
$hash = crypt($text, $prefix.$salt);
if (is_string_empty($salt) || strlenb($hash)!=60) $hash = "";
break;
case "sha256": $prefix = "$5y$";
$salt = $this->createSalt(32);
$hash = "$prefix$salt".hash("sha256", $salt.$text);
if (is_string_empty($salt) || strlenb($hash)!=100) $hash = "";
break;
}
return $hash;
}
// Verify that text matches hash
public function verifyHash($text, $algorithm, $hash) {
$hashCalculated = "";
switch ($algorithm) {
case "bcrypt": if (substrb($hash, 0, 4)=="$2y$" || substrb($hash, 0, 4)=="$2a$") {
$hashCalculated = crypt($text, $hash);
}
break;
case "sha256": if (substrb($hash, 0, 4)=="$5y$") {
$prefix = "$5y$";
$salt = substrb($hash, 4, 32);
$hashCalculated = "$prefix$salt".hash("sha256", $salt.$text);
}
break;
}
return $this->verifyToken($hashCalculated, $hash);
}
// Verify that token is not empty and identical, timing attack safe string comparison
public function verifyToken($tokenExpected, $tokenReceived) {
$ok = false;
$lengthExpected = strlenb($tokenExpected);
$lengthReceived = strlenb($tokenReceived);
if ($lengthExpected!=0 && $lengthReceived!=0) {
$ok = $lengthExpected==$lengthReceived;
for ($i=0; $i<$lengthReceived; ++$i) {
$ok &= $tokenExpected[$i<$lengthExpected ? $i : 0]==$tokenReceived[$i];
}
}
return $ok;
}
// Return meta data from raw data
public function getMetaData($rawData, $key) {
$value = "";
if (preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)\-\-\-[\r\n]+(.*)$/s", $rawData, $parts)) {
$key = lcfirst($key);
foreach ($this->getTextLines($parts[2]) as $line) {
if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
if (lcfirst($matches[1])==$key && !is_string_empty($matches[2])) {
$value = $matches[2];
break;
}
}
}
}
return $value;
}
// Set meta data in raw data
public function setMetaData($rawData, $key, $value) {
if (preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)\-\-\-[\r\n]+(.*)$/s", $rawData, $parts)) {
$found = false;
$key = lcfirst($key);
$rawDataMiddle = "";
foreach ($this->getTextLines($parts[2]) as $line) {
if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
if (lcfirst($matches[1])==$key) {
$rawDataMiddle .= "$matches[1]: $value\n";
$found = true;
continue;
}
}
$rawDataMiddle .= $line;
}
if (!$found) $rawDataMiddle .= (strposu($key, "/") ? $key : ucfirst($key)).": $value\n";
$rawDataNew = $parts[1]."---\n".$rawDataMiddle."---\n".$parts[3];
} else {
$rawDataNew = $rawData;
}
return $rawDataNew;
}
// Remove meta data in raw data
public function unsetMetaData($rawData, $key) {
if (preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)\-\-\-[\r\n]+(.*)$/s", $rawData, $parts)) {
$key = lcfirst($key);
$rawDataMiddle = "";
foreach ($this->getTextLines($parts[2]) as $line) {
if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
if (lcfirst($matches[1])==$key) continue;
}
$rawDataMiddle .= $line;
}
$rawDataNew = $parts[1]."---\n".$rawDataMiddle."---\n".$parts[3];
} else {
$rawDataNew = $rawData;
}
return $rawDataNew;
}
// Return troubleshooting URL
public function getTroubleshootingUrl() {
return "https://datenstrom.se/yellow/help/troubleshooting";
}
// Detect server URL
public function detectServerUrl() {
$scheme = "http";
if ($this->getServer("REQUEST_SCHEME")=="https" || $this->getServer("HTTPS")=="on") $scheme = "https";
if ($this->getServer("HTTP_X_FORWARDED_PROTO")=="https") $scheme = "https";
$address = $this->getServer("SERVER_NAME");
$port = $this->getServer("SERVER_PORT");
if ($port!=80 && $port!=443) $address .= ":$port";
$base = "";
if (preg_match("/^(.*)\/.*\.php$/", $this->getServer("SCRIPT_NAME"), $matches)) $base = $matches[1];
return "$scheme://$address$base/";
}
// Detect server location
public function detectServerLocation() {
if (isset($_SERVER["REQUEST_URI"])) {
$location = $_SERVER["REQUEST_URI"];
$location = rawurldecode(($pos = strposu($location, "?")) ? substru($location, 0, $pos) : $location);
$location = $this->yellow->lookup->normalisePath($location);
if (substru($location, 0, 1)!="/") $location = "/".$location;
$separator = $this->getLocationArgumentsSeparator();
if (preg_match("/^(.*?\/)([^\/]+$separator.*)$/", $location, $matches)) {
$_SERVER["LOCATION"] = $location = $matches[1];
$_SERVER["LOCATION_ARGUMENTS"] = $matches[2];
foreach (explode("/", $matches[2]) as $token) {
if (preg_match("/^(.*?)$separator(.*)$/", $token, $matches)) {
if (!is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
$matches[1] = str_replace(array("\x1c", "\x1d", "\x1e"), array("/", ":", "="), $matches[1]);
$matches[2] = str_replace(array("\x1c", "\x1d", "\x1e"), array("/", ":", "="), $matches[2]);
$_REQUEST[$matches[1]] = $matches[2];
}
}
}
} else {
$_SERVER["LOCATION"] = $location;
$_SERVER["LOCATION_ARGUMENTS"] = "";
}
}
return $this->getServer("LOCATION");
}
// Detect server sitename
public function detectServerSitename() {
$sitename = "Localhost";
if (preg_match("#^(www\.)?([\w\-]+)#", $this->getServer("SERVER_NAME"), $matches)) {
$sitename = ucfirst($matches[2]);
}
return $sitename;
}
// Detect server timezone
public function detectServerTimezone() {
$timezone = ini_get("date.timezone");
if (is_string_empty($timezone)) {
if (PHP_OS=="Darwin") {
if (preg_match("#zoneinfo/(.*)#", @readlink("/etc/localtime"), $matches)) $timezone = $matches[1];
} else {
if (preg_match("/^(\S+)\/(\S+)/", $this->readFile("/etc/timezone"), $matches)) $timezone = $matches[1];
}
}
if (!in_array($timezone, timezone_identifiers_list())) $timezone = "UTC";
return $timezone;
}
// Detect server name, version and operating system
public function detectServerInformation() {
$name = "Unknown";
$version = "x.x.x";
$os = PHP_OS;
if (preg_match("/^(\S+)\/(\S+)/", $this->getServer("SERVER_SOFTWARE"), $matches)) {
$name = $matches[1];
$version = $matches[2];
} elseif (preg_match("/^(\S+)/", $this->getServer("SERVER_SOFTWARE"), $matches)) {
$name = $matches[1];
}
if (PHP_SAPI=="cli" || PHP_SAPI=="cli-server") {
$name = "Built-in";
$version = PHP_VERSION;
}
if (PHP_OS=="Darwin") {
$os = "Mac";
} elseif (strtoupperu(substru(PHP_OS, 0, 3))=="WIN") {
$os = "Windows";
}
return array($name, $version, $os);
}
// Detect browser language
public function detectBrowserLanguage($languages, $languageDefault) {
$languageFound = $languageDefault;
foreach (preg_split("/\s*,\s*/", $this->getServer("HTTP_ACCEPT_LANGUAGE")) as $text) {
list($language, $dummy) = $this->getTextList($text, ";", 2);
if (!is_string_empty($language) && in_array($language, $languages)) {
$languageFound = $language;
break;
}
}
return $languageFound;
}
// Detect terminal width and height
public function detectTerminalInformation() {
$width = $height = 0;
if (strtoupperu(substru(PHP_OS, 0, 3))=="WIN") {
exec("powershell \$Host.UI.RawUI.WindowSize.Width", $outputLines, $returnStatus);
if ($returnStatus==0 && !is_array_empty($outputLines)) {
$width = intval(end($outputLines));
}
exec("powershell \$Host.UI.RawUI.WindowSize.Height", $outputLines, $returnStatus);
if ($returnStatus==0 && !is_array_empty($outputLines)) {
$height = intval(end($outputLines));
}
} else {
exec("stty size", $outputLines, $returnStatus);
if ($returnStatus==0 && preg_match("/^(\d+)\s+(\d+)/", implode("\n", $outputLines), $matches)) {
$width = intval($matches[2]);
$height = intval($matches[1]);
}
}
return array($width, $height);
}
// Detect image width, height, orientation and type for GIF/JPG/PNG/SVG
public function detectImageInformation($fileName, $fileType = "") {
$width = $height = $orientation = 0;
$type = "";
$fileHandle = @fopen($fileName, "rb");
if ($fileHandle) {
if (is_string_empty($fileType)) $fileType = $this->getFileType($fileName);
if ($fileType=="gif") {
$dataSignature = fread($fileHandle, 6);
$dataHeader = fread($fileHandle, 7);
if (!feof($fileHandle) && ($dataSignature=="GIF87a" || $dataSignature=="GIF89a")) {
$width = (ord($dataHeader[1])<<8) + ord($dataHeader[0]);
$height = (ord($dataHeader[3])<<8) + ord($dataHeader[2]);
$type = $fileType;
}
} elseif ($fileType=="jpg") {
$dataBufferSizeMax = filesize($fileName);
$dataBufferSize = min($dataBufferSizeMax, 4096);
if ($dataBufferSize) $dataBuffer = fread($fileHandle, $dataBufferSize);
$dataSignature = substrb($dataBuffer, 0, 2);
if (!feof($fileHandle) && $dataSignature=="\xff\xd8") {
for ($pos=2; $pos+8<$dataBufferSize; $pos+=$length) {
if ($dataBuffer[$pos]!="\xff") break;
$dataMarker = $dataBuffer[$pos+1];
if ($dataMarker=="\xe1") {
$orientation = $this->getImageOrientationFromBuffer($dataBuffer, $pos+4, $dataBufferSize);
}
if (($dataMarker>="\xc0" && $dataMarker<="\xc3") ||
($dataMarker>="\xc5" && $dataMarker<="\xc7") ||
($dataMarker>="\xc9" && $dataMarker<="\xcb") ||
($dataMarker>="\xcd" && $dataMarker<="\xcf")) {
$width = (ord($dataBuffer[$pos+7])<<8) + ord($dataBuffer[$pos+8]);
$height = (ord($dataBuffer[$pos+5])<<8) + ord($dataBuffer[$pos+6]);
$type = $fileType;
break;
}
$length = (ord($dataBuffer[$pos+2])<<8) + ord($dataBuffer[$pos+3]) + 2;
while ($pos+$length+8>=$dataBufferSize) {
if ($dataBufferSize==$dataBufferSizeMax) break;
$dataBufferDiff = min($dataBufferSizeMax, $dataBufferSize*2) - $dataBufferSize;
$dataBufferSize += $dataBufferDiff;
$dataBufferChunk = fread($fileHandle, $dataBufferDiff);
if (feof($fileHandle) || $dataBufferChunk===false) {
$dataBufferSize = 0;
break;
}
$dataBuffer .= $dataBufferChunk;
}
}
}
} elseif ($fileType=="png") {
$dataSignature = fread($fileHandle, 8);
$dataHeader = fread($fileHandle, 16);
if (!feof($fileHandle) && $dataSignature=="\x89PNG\r\n\x1a\n") {
$width = (ord($dataHeader[10])<<8) + ord($dataHeader[11]);
$height = (ord($dataHeader[14])<<8) + ord($dataHeader[15]);
$type = $fileType;
}
} elseif ($fileType=="svg") {
$dataBufferSizeMax = filesize($fileName);
$dataBufferSize = min($dataBufferSizeMax, 4096);
if ($dataBufferSize) $dataBuffer = fread($fileHandle, $dataBufferSize);
if (!feof($fileHandle) && preg_match("/