/** * Constuct Layout * @return string */ protected function constructLayout() { //HTML5 DocType $layout = "<!DOCTYPE html>\n"; //HTML $currentUrlClass = "url-" . str_replace("/", "-", trim($this->controller->request()->url(), "/") ?: "index"); $html = $this->_htmlTag->addClass($this->createBrowserClassString())->addClass($currentUrlClass); //HEAD $head = HTML::head($this->constructHead()); //BODY $body = $this->_bodyTag->content($this->constructBody()); //COMBINE $layout .= $html->content($head . "\n" . $body)->render(); return $layout; }
/** * 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(); } } }