<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 1.0.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace DebugKit\View\Helper;
use Cake\Core\Configure;
use Cake\Error\Debugger;
use Cake\Filesystem\File;
use Cake\View\Helper;
/**
* TidyHelper class
*
* Passes html through tidy on the command line, and reports markup errors
*
* @uses AppHelper
* @since v 1.0 (22-Jun-2009)
*
* @property \DebugKit\View\Helper\ToolbarHelper $Toolbar
*/
class TidyHelper extends Helper
{
/**
* helpers property
*
* @var array
*/
public $helpers = ['DebugKit.Toolbar'];
/**
* results property
*
* @var mixed null
*/
public $results = null;
/**
* Return a nested array of errors for the passed html string
* Fudge the markup slightly so that the tag which is invalid is highlighted
*
* @param string $html ''
* @param string $out ''
* @return array
*/
public function process($html = '', &$out = '')
{
$errors = $this->tidyErrors($html, $out);
if (!$errors) {
return [];
}
$result = ['Error' => [], 'Warning' => [], 'Misc' => []];
$errors = explode("\n", $errors);
$markup = explode("\n", $out);
foreach ($errors as $error) {
preg_match('@line (\d+) column (\d+) - (\w+): (.*)@', $error, $matches);
if ($matches) {
list($original, $line, $column, $type, $message) = $matches;
$line = $line - 1;
$string = '</strong>';
if (isset($markup[$line - 1])) {
$string .= h($markup[$line - 1]);
}
// phpcs:ignore
$string .= '<strong>' . h(@$markup[$line]) . '</strong>';
if (isset($markup[$line + 1])) {
$string .= h($markup[$line + 1]);
}
$string .= '</strong>';
$result[$type][$string][] = h($message);
} elseif ($error) {
$message = $error;
$result['Misc'][h($message)][] = h($message);
}
}
$this->results = $result;
return $result;
}
/**
* report method
*
* Call process if a string is passed, or no prior results exist - and return the results using
* the toolbar helper to generate a nested navigatable array
*
* @param mixed $html null
* @return string
*/
public function report($html = null)
{
if ($html) {
$this->process($html);
} elseif ($this->results === null) {
$this->process($this->_View->output);
}
if (!$this->results) {
return '<p>' . __d('debug_kit', 'No markup errors found') . '</p>';
}
foreach ($this->results as &$results) {
foreach ($results as $type => &$messages) {
foreach ($messages as &$message) {
$message = html_entity_decode($message, ENT_COMPAT, Configure::read('App.encoding'));
}
}
}
return $this->Toolbar->makeNeatArray(array_filter($this->results), 0, 0, false);
}
/**
* Run the html string through tidy, and return the (raw) errors. pass back a reference to the
* normalized string so that the error messages can be linked to the line that caused them.
*
* @param string $in ''
* @param string $out ''
* @return string
*/
public function tidyErrors($in = '', &$out = '')
{
$out = preg_replace('@>\s*<@s', ">\n<", $in);
// direct access? windows etc
if (function_exists('tidy_parse_string')) {
$tidy = tidy_parse_string($out, [], 'UTF8');
$tidy->cleanRepair();
$errors = $tidy->errorBuffer . "\n";
return $errors;
}
// cli
$File = new File(rtrim(TMP, DS) . DS . rand() . '.html', true);
$File->write($out);
$path = $File->pwd();
$errors = $path . '.err';
$this->_exec("tidy -eq -utf8 -f $errors $path");
$File->delete();
if (!file_exists($errors)) {
return '';
}
$Error = new File($errors);
$errors = $Error->read();
$Error->delete();
return $errors;
}
/**
* exec method
*
* @param mixed $cmd ''
* @param mixed $out null
* @return bool True if successful
*/
protected function _exec($cmd, &$out = null)
{
if (DS === '/') {
$_out = exec($cmd . ' 2>&1', $out, $return);
} else {
$_out = exec($cmd, $out, $return);
}
if (Configure::read('debug')) {
$source = Debugger::trace(['depth' => 1, 'start' => 2]) . "\n";
//Log::write('system_calls_' . date('Y-m-d'), "\n" . $source . Debugger::exportVar(compact('cmd','out','return')));
//Log::write('system_calls', "\n" . $source . Debugger::exportVar(compact('cmd','out','return')));
}
if ($return) {
return false;
}
return $_out ? $_out : true;
}
}