/** * Init * This method must return self * @return self */ public function init() { parent::init(); $reflector = new \ReflectionClass($this); $this->_applicationNamespace = $reflector->getNamespaceName(); //Define BASE Asset paths $assetConfig = $this->config()->get("assets"); $assetPath = $assetConfig->get("assets", "assets"); if (!defined("BASE_ASSETS")) { define("BASE_ASSETS", Router::buildPath(SITE_URL, $assetPath)); } if (!defined("BASE_IMAGES")) { define("BASE_IMAGES", Router::buildPath(SITE_URL, $assetPath, $assetConfig->get("images", "images"))); } if (!defined("BASE_STYLES")) { define("BASE_STYLES", Router::buildPath(SITE_URL, $assetPath, $assetConfig->get("css", "css"))); } if (!defined("BASE_SCRIPTS")) { define("BASE_SCRIPTS", Router::buildPath(SITE_URL, $assetPath, $assetConfig->get("js", "js"))); } if (!defined("BASE_TEMPLATES")) { define("BASE_TEMPLATES", Filesystem::buildPath(PROJECT_PATH, $assetConfig->get("templates", "Templates"))); } return $this; }
/** * Request * @param requestMethod * @param requestEndpoint * @param requestBody * @return VOID */ public function request($requestMethod, $requestEndpoint, $requestBody = NULL, $requestHeaders = []) { if (!$requestMethod || !$requestEndpoint) { throw new \InvalidArgumentException("Missing Arguments"); } $this->addHeaders($requestHeaders); if (Router::isDev()) { ob_start(); \pre_r($requestMethod, $requestEndpoint, $requestBody, $this->getAllHeaders()); $apiLog = ob_get_contents(); ob_end_clean(); error_log($apiLog); } $c = curl_init(); curl_setopt($c, CURLOPT_URL, $requestEndpoint); curl_setopt($c, CURLOPT_TIMEOUT, 30); curl_setopt($c, CURLOPT_USERAGENT, 'touchbase-php-library/2.0'); curl_setopt($c, CURLOPT_RETURNTRANSFER, true); curl_setopt($c, CURLINFO_HEADER_OUT, true); curl_setopt($c, CURLOPT_HTTPHEADER, $this->getAllHeaders()); curl_setopt($c, CURLOPT_CUSTOMREQUEST, strtoupper($requestMethod)); curl_setopt($c, CURLOPT_POSTFIELDS, $requestBody); curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false); $this->_lastResponse = curl_exec($c); $this->_lastResponseStatus = curl_getinfo($c, CURLINFO_HTTP_CODE); $this->_lastResponseContentType = curl_getinfo($c, CURLINFO_CONTENT_TYPE); $curlError = curl_error($c); if ($curlError) { throw new \RuntimeException(is_string($curlError) ? $curlError : "Something Terrible Happened!"); } return $this->_lastResponse; }
/** * _configure * @param \Touchbase\Core\Config\Store * @return Touchbase\Core\Config\Store */ private function _configure(ConfigStore $config) { $ns = $src = ""; try { //Load Main Configuration File $configurationData = IniConfigProvider::create()->parseIniFile(File::create([BASE_PATH, 'config.ini'])); $config->addConfig($configurationData->getConfiguration()); $ns = $config->get("project")->get("namespace", "Project"); $src = $config->get("project")->get("source", "src"); //Load Extra Configuration Files $loadExtraConfig = function ($files, $configFilePath = BASE_PATH) use(&$loadExtraConfig, &$config) { if (!empty($files)) { foreach ($files as $condition => $file) { $extraConfigFile = File::create([$configFilePath, $file]); //Not a domain, path or environment - load always if (is_numeric($condition)) { if ($extraConfigFile->exists()) { $configurationData = IniConfigProvider::create()->parseIniFile($extraConfigFile); $config->addConfig($extraConfig = $configurationData->getConfiguration()); $loadExtraConfig($extraConfig->get("config")->get("files", ""), File::buildPath($configFilePath, dirname($file))); } //We want to match a certain condition } else { if ((!Router::isCLI() && strpos(@$_SERVER['HTTP_X_FORWARDED_HOST'] ?: $_SERVER['HTTP_HOST'], $condition) !== false || !Router::isCLI() && strpos($_SERVER["REQUEST_URI"], $condition) === 0 || !Router::isCLI() && strpos($_SERVER["SERVER_NAME"], $condition) === 0 || strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN' && $condition == "windows" || defined("TOUCHBASE_ENV") && TOUCHBASE_ENV == $condition) && $extraConfigFile->exists()) { $configurationData = IniConfigProvider::create()->parseIniFile($extraConfigFile); $config->addConfig($extraConfig = $configurationData->getConfiguration()); $loadExtraConfig($extraConfig->get("config")->get("files", ""), File::buildPath($configFilePath, dirname($file))); } } } } }; $loadExtraConfig($config->get("config")->get("files", "")); StaticStore::shared()->set(ConfigStore::CONFIG_KEY, $config); } catch (\Exception $e) { } if (!defined('PROJECT_PATH')) { $psr0 = realpath(File::buildPath(BASE_PATH, $src, $ns)); $psr4 = realpath(File::buildPath(BASE_PATH, $src)); define('PROJECT_PATH', $psr0 ?: $psr4); } return $config; }
/** * Strip Url Parameters * @oaram string $urk * @param array $excludedParams * @return string */ public static function stripUrlParameters($url, $excludedParams = []) { $parsedUrl = @parse_url($url); if (!empty($parsedUrl)) { if (!empty($parsedUrl['host'])) { $parsedUrl['host'] = mb_strtolower($parsedUrl['host'], 'UTF-8'); } if (!empty($parsedUrl['fragment'])) { $fragment =& $parsedUrl['fragment']; if (substr($fragment, -1) == '#') { $fragment = substr($fragment, 0, strlen($fragment) - 1); } } if (!empty($parsedUrl['query'])) { if (count($excludedParams)) { parse_str($parsedUrl['query'], $params); $parsedUrl['query'] = http_build_query(array_diff_key($params, array_flip($excludedParams))); } else { unset($parsedUrl['query']); } } } return Router::buildUrl($parsedUrl); }
/** * Path For Asset Url * @param string assetUrl * @return string */ public static function pathForAssetUrl($assetUrl, $assetType = null) { if (Router::isSiteUrl($assetUrl)) { list($assetMapFragment, $assetUrl) = array_pad(explode("/", Router::relativeUrl($assetUrl), 2), 2, null); $file = File::create([BASE_PATH, static::pathForAssetMap($assetMapFragment), $assetUrl]); if ($file->exists()) { return $file->path; } // //Is it a folder? // $folder = Folder::create([BASE_PATH, static::pathForAssetMap($assetMapFragment), $assetUrl]); // if($folder->exists()){ // return $folder->path; // } return null; } return $assetUrl; }
/** * Redirect * Redirect the response * @param string $destinationUrl * @param int $statusCode * @return \Touchbase\Control\HTTPResponse */ public function redirect($destinationUrl, $statusCode = 302) { if (is_numeric($destinationUrl) && $destinationUrl < 0) { $routeHistory = Router::routeHistory(); $destinationUrl = $routeHistory[count($routeHistory) - abs($destinationUrl)]; } if (Router::isRelativeURL($destinationUrl)) { $destinationUrl = Router::buildPath(SITE_URL, $destinationUrl); } $statusCode = in_array($statusCode, $this->redirectCodes) ? $statusCode : 302; $this->setStatusCode($statusCode); $this->addHeader('Location', $destinationUrl); return $this; }
/** * Handler * This method handles the displaying of the error message * @param int $errorType * @param string $errorMessage * @param string $errorFile * @param int $errorLine * @param array $errorContext * @return VOID */ private function handler($errorType, $errorMessage, $errorFile = null, $errorLine = 0, $errorContext = array()) { if (in_array($errorType, array(E_CORE_WARNING, E_CORE_ERROR))) { return; } //Lets add the real line and file number to errors that have been thrown via `trigger_error` if (in_array($errorType, self::$userErrors)) { $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4 + (self::$callerBacktraceBump ? self::$callerBacktraceBump-- : 0)); $caller = end($backtrace); $errorFile = $caller['file']; $errorLine = $caller['line']; } error_log(sprintf($cliError = "PHP %s: %s in %s on line %d", $this->errorText[$errorType], $errorMessage, $errorFile, $errorLine)); print sprintf(Router::isCLI() ? $cliError : "<pre><strong>%s:</strong> %s in <strong>%s</strong> on line <strong>%d</strong></pre>", $this->errorText[$errorType], $errorMessage, $errorFile, $errorLine); //Kill the application if required. if (in_array($errorType, self::$terminalErrors)) { exit; } }
/** * Asset Include Snipit * This will find an asset and import it with the correct html tag * @param string $assetType * @param string $file * @param array $options * @return string - The html snipit */ private function assetIncludeSnipit($assetType, $file, $options) { foreach ($this->assetSearchPaths($assetType) as $path) { if (Router::pathForAssetUrl($filePath = Router::buildPath($path, $file), $assetType)) { switch ($assetType) { case self::CSS: return HTML::link()->attr($options)->attr(["rel" => "stylesheet", "type" => "text/css", "href" => $filePath]); break; case self::JS: return HTML::script()->attr($options)->attr(["type" => "text/javascript", "src" => $filePath]); break; case self::IMG: return HTML::img()->attr($options)->attr(["src" => $filePath]); break; } } } return NULL; }
/** * Validate Html * This method scans the outgoing HTML for any forms, if found it will save the form to the session in order to validate. * If any errors previously existed in the session, this method will apply `bootstrap` style css error classes. * @pararm string &$htmlDocument - The outgoing HTML string * @return VOID */ private function validateHtml(&$htmlDocument) { if (!empty($htmlDocument)) { $dom = new DOMDocument(); $dom->loadHtml($htmlDocument, LIBXML_NOWARNING | LIBXML_NOERROR | LIBXML_NOENT | LIBXML_HTML_NOIMPLIED); //Automatically apply an active class to links that relate to the current URL foreach ($dom->getElementsByTagName('a') as $link) { if (Router::isSiteUrl($href = Router::relativeURL($link->getAttribute("href")))) { $currentClasses = explode(" ", $link->getAttribute("class")); if (strcasecmp($href, $this->controller->request()->url()) == 0) { $currentClasses[] = 'active'; } if (strpos($this->controller->request()->url(), $href) === 0) { $currentClasses[] = 'child-active'; } $link->setAttribute('class', implode(" ", $currentClasses)); } } //Save the outgoing form elements for future validation. foreach ($dom->getElementsByTagName('form') as $form) { $savedom = new \DOMDocument(); if ($formAction = $form->getAttribute("action") && !Router::isSiteUrl($formAction)) { continue; } //Add CSRF $rand = function_exists('random_bytes') ? random_bytes(32) : null; $rand = !$rand && function_exists('mcrypt_create_iv') ? mcrypt_create_iv(32, MCRYPT_DEV_URANDOM) : $rand; $rand = $rand ? $rand : openssl_random_pseudo_bytes(32); $csrfToken = bin2hex($rand); $formName = $form->getAttribute("name"); $formNameToken = $formName . "_" . $csrfToken; $csrf = $dom->createDocumentFragment(); $csrf->appendXML(HTML::input()->attr("type", "hidden")->attr("name", "tb_form_token")->attr("value", $formNameToken)->attr("readonly", true)); $form->insertBefore($csrf, $form->firstChild); //TODO: Should we assume the form will allways have content foreach (["input", "textarea", "select"] as $tag) { foreach ($form->getElementsByTagName($tag) as $input) { $savedom->appendChild($savedom->importNode($input->cloneNode())); //Populate form with previous data if (($newValue = SessionStore::get("touchbase.key.session.post")->get($input->getAttribute("name"), false)) !== false) { if (is_scalar($newValue) && $input->getAttribute("type") !== "hidden" && !$input->hasAttribute("readonly")) { $input->setAttribute('value', $newValue); } } //Populate errors if ($errorMessage = $this->controller->errors($formName)->get($input->getAttribute("name"), false)) { $currentClasses = explode(" ", $input->parentNode->getAttribute("class")); foreach (["has-feedback", "has-error"] as $class) { if (!in_array($class, $currentClasses)) { $currentClasses[] = $class; } } $input->parentNode->setAttribute('class', implode(" ", $currentClasses)); $input->setAttribute("data-error", $errorMessage); } } } SessionStore::recycle($formName, $formNameToken, base64_encode(gzdeflate($savedom->saveHTML(), 9))); } //Move body scripts to bottom $bodies = $dom->getElementsByTagName('body'); $body = $bodies->item(0); if ($body) { foreach ($body->getElementsByTagName('script') as $script) { if ($script->parentNode->nodeName === "body") { break; } $body->appendChild($dom->importNode($script)); } } //Look for the special attribute that moves nodes. //This is useful for moving modals from the template files to the bottom output. $xpath = new \DOMXPath($dom); $appendToBodyRef = NULL; foreach ($xpath->query("//*[@tb-append]") as $element) { $appendTo = $xpath->query($element->getAttribute("tb-append"))->item(0); $element->removeAttribute("tb-append"); if ($appendTo) { if ($appendTo->nodeName === "body") { //Special case to append above the included javascript files. if (!$appendToBodyRef) { $appendToBodyRef = $xpath->query('/html/body/comment()[. = " END CONTENT "][1]')->item(0); } $body->insertBefore($dom->importNode($element), $appendToBodyRef); } else { $appendTo->appendChild($dom->importNode($element)); } } } //Save the HTML with the updates. if ($this->controller->request()->isAjax() || !$this->controller->request()->isMainRequest()) { //This will remove the doctype that's automatically appended. $htmlDocument = $dom->saveHTML($dom->documentElement); } else { $htmlDocument = $dom->saveHTML(); } } }