Example #1
0
 /**
  *	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();
         }
     }
 }