![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/cartforge.co/vendor/phpmd/phpmd/src/main/php/PHPMD/Renderer/ |
<?php /** * This file is part of PHP Mess Detector. * * Copyright (c) Manuel Pichler <[email protected]>. * All rights reserved. * * Licensed under BSD License * For full copyright and license information, please see the LICENSE file. * Redistributions of files must retain the above copyright notice. * * @author Manuel Pichler <[email protected]> * @copyright Manuel Pichler. All rights reserved. * @license https://opensource.org/licenses/bsd-license.php BSD License * @link http://phpmd.org/ */ namespace PHPMD\Renderer; use PHPMD\AbstractRenderer; use PHPMD\Report; use SplFileObject; /** * This renderer output a html file with all found violations. * * @author Premysl Karbula <[email protected]> * @copyright 2017 Premysl Karbula. All rights reserved. * @license http://www.opensource.org/licenses/bsd-license.php BSD License */ class HTMLRenderer extends AbstractRenderer { const CATEGORY_PRIORITY = 'category_priority'; const CATEGORY_NAMESPACE = 'category_namespace'; const CATEGORY_RULESET = 'category_ruleset'; const CATEGORY_RULE = 'category_rule'; protected static $priorityTitles = array( 1 => 'Top (1)', 2 => 'High (2)', 3 => 'Moderate (3)', 4 => 'Low (4)', 5 => 'Lowest (5)', ); // Used in self::colorize() method. protected static $descHighlightRules = array( 'method' => array( // Method names. 'regex' => 'method\s+(((["\']).*["\'])|(\S+))', 'css-class' => 'hlt-method', ), 'quoted' => array( // Quoted strings. 'regex' => '(["\'][^\'"]+["\'])', 'css-class' => 'hlt-quoted', ), 'variable' => array( // Variables. 'regex' => '(\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)', 'css-class' => 'hlt-variable', ), ); protected static $compiledHighlightRegex = null; /** * Specify how many extra lines are added to a code snippet * By default 2 * @var int */ protected $extraLineInExcerpt = 2; public function __construct($extraLineInExcerpt = null) { if ($extraLineInExcerpt && is_int($extraLineInExcerpt)) { $this->extraLineInExcerpt = $extraLineInExcerpt; } } /** * This method will be called on all renderers before the engine starts the * real report processing. * * @return void */ public function start() { $writer = $this->getWriter(); $mainColor = "#2f838a"; // Avoid inlining styles. $style = " <script> function toggle(id) { var item = document.getElementById(id); item.classList.toggle('hidden'); } </script> <style> @media (min-width: 1366px) { body { max-width: 80%; margin: auto; } } body { font-family: sans-serif; } a { color: $mainColor; } a:hover { color: #333; } em { font-weight: bold; font-style: italic; } h1 { padding: 0.5ex 0.2ex; border-bottom: 2px solid #333; } table { width: 100%; border-spacing: 0; } table tr > th { text-align: left; } table caption { font-weight: bold; padding: 1ex 0.5ex; text-align: left; font-size: 120%; border-bottom: 2px solid #333; } tbody tr:nth-child(odd) { background: rgba(0, 0, 0, 0.08); } tbody tr:hover { background: #ffee99; } thead th { border-bottom: 1px solid #aaa; } table td, table th { padding: 0.5ex; } /* Table 'count' and 'percentage' column */ .t-cnt, .t-pct { width: 5em; } .t-pct { opacity: 0.8; font-style: italic; font-size: 80%; } /* Table bar chart */ .t-bar { height: 0.5ex; margin-top: 0.5ex; background-color: $mainColor; /* rgba(47, 131, 138, 0.2); */ } section, table { margin-bottom: 2em; } #details-link.hidden { display: none; } #details-wrapper.hidden { display: none; } ul.code { margin: 0; padding: 0; } ul.code.hidden { display: none; } ul.code li { display: flex; line-height: 1.4em; font-family: monospace; white-space: nowrap; } ul.code li:nth-child(odd) { background-color: rgba(47, 131, 138, 0.1) } /* Excerpt: Line number */ ul.code .no { width: 5%; min-width: 5em; text-align: right; border-right: 1px solid rgba(47, 131, 138, 0.6); padding-right: 1ex; box-sizing: border-box; } /* Excerpt: Code */ ul.code .cd { padding-left: 1ex; white-space: pre-wrap; box-sizing: border-box; word-wrap: break-word; overflow: hidden; } .hlt { background: #ffee99 !important } .prio { color: #333; float: right; } .indx { padding: 0.5ex 1ex; background-color: #000; color: #fff; text-decoration: none; } .indx:hover { background-color: $mainColor; color: #fff; } /* Problem container */ .prb h3 { padding: 1ex 0.5ex; border-bottom: 2px solid #000; font-size: 95%; margin: 0; } .info-lnk { font-style: italic !important; font-weight: normal !important; text-decoration: none; } .info-lnk.blck { padding: 0.5ex 1ex; background-color: rgba(47, 131, 138, 0.2); } .path-basename { font-weight: bold; } .hlt-info { display: inline-block; padding: 2px 4px; font-style: italic; } .hlt-info.quoted { background-color: #92de71; } .hlt-info.variable { background-color: #a3d2ff; } .hlt-info.method { background-color: #f7c0ff; } .sub-info { padding: 1ex 0.5ex; } /* Handle printer friendly styles */ @media print { body, th { font-size: 10pt; } .hlt-info { padding: 0; background: none; } section, table { margin-bottom: 1em; } h1, h2, h3, table caption { padding: 0.5ex 0.2ex; } .prb h3 { border-bottom: 0.5px solid #aaa; } .t-bar { display: none; } .info-lnk { display: none; } #details-wrapper { display: block !important; font-size: 90% !important; } } </style>"; $style = self::reduceWhitespace($style); $writer->write("<html><head>{$style}<title>PHPMD Report</title></head><body>" . PHP_EOL); $header = sprintf(" <header> <h1>PHPMD Report</h1> Generated at <em>%s</em> with <a href='%s' target='_blank'>PHP Mess Detector</a> on <em>PHP %s</em> on <em>%s</em> </header> ", date('Y-m-d H:i'), "https://phpmd.org", \PHP_VERSION, gethostname()); $writer->write($header); } /** * This method will be called when the engine has finished the source analysis * phase. * * @param \PHPMD\Report $report * @return void */ public function renderReport(Report $report) { $writer = $this->getWriter(); $index = 0; $violations = $report->getRuleViolations(); $count = count($violations); $writer->write(sprintf('<h3>%d problems found</h3>', $count)); // If no problems were found, don't bother with rendering anything else. if (!$count) { return; } // Render summary tables. $writer->write("<h2>Summary</h2>"); $categorized = self::sumUpViolations($violations); $this->writeTable('By priority', 'Priority', $categorized[self::CATEGORY_PRIORITY]); $this->writeTable('By namespace', 'PHP Namespace', $categorized[self::CATEGORY_NAMESPACE]); $this->writeTable('By rule set', 'Rule set', $categorized[self::CATEGORY_RULESET]); $this->writeTable('By name', 'Rule name', $categorized[self::CATEGORY_RULE]); // Render details of each violation and place the "Details" display toggle. $writer->write("<h2 style='page-break-before: always'>Details</h2>"); $writer->write(" <a id='details-link' class='info-lnk blck' href='#' onclick='toggle(\"details-link\"); toggle(\"details-wrapper\"); return false;' > Show details ▼ </a>"); $writer->write("<div id='details-wrapper' class='hidden'>"); foreach ($violations as $violation) { // This is going to be used as ID in HTML (deep anchoring). $htmlId = "p-" . $index++; // Get excerpt of the code from validated file. $excerptHtml = null; $excerpt = self::getLineExcerpt( $violation->getFileName(), $violation->getBeginLine(), $this->extraLineInExcerpt ); foreach ($excerpt as $line => $code) { $class = $line === $violation->getBeginLine() ? " class='hlt'" : null; $codeHtml = htmlspecialchars($code); $excerptHtml .= "<li{$class}><div class='no'>{$line}</div><div class='cd'>{$codeHtml}</div></li>"; } $descHtml = self::colorize(htmlentities($violation->getDescription())); $filePath = $violation->getFileName(); $fileHtml = "<a href='file://$filePath' target='_blank'>" . self::highlightFile($filePath) . "</a>"; // Create an external link to rule's help, if there's any provided. $linkHtml = null; $url = $violation->getRule()->getExternalInfoUrl(); if ($url) { $linkHtml = "<a class='info-lnk' href='{$url}' target='_blank'>(help)</a>"; } // HTML snippet handling the toggle to display the file's code. $showCodeAnchor = " <a class='info-lnk blck' href='#' onclick='toggle(\"$htmlId-code\"); return false;'> Show code ▼ </a>"; $prio = self::$priorityTitles[$violation->getRule()->getPriority()]; $html = " <section class='prb' id='$htmlId'> <header> <h3> <a href='#$htmlId' class='indx'>#{$index}</a> {$descHtml} {$linkHtml} <span class='prio'>{$prio}</span> </h3> </header> <div class='sub-info'><b>File:</b> {$fileHtml} {$showCodeAnchor}</div> <ul class='code hidden' id='$htmlId-code'>%s</ul> </section>"; // Remove unnecessary tab/space characters at the line beginnings. $html = self::reduceWhitespace($html); $writer->write(sprintf($html, $excerptHtml)); } } /** * This method will be called the engine has finished the report processing * for all registered renderers. * * @return void */ public function end() { $writer = $this->getWriter(); $writer->write('</div></body></html>'); } /** * Return array of lines from a specified file:line, optionally with extra lines around * for additional cognitive context. * * @return array */ protected static function getLineExcerpt($file, $lineNumber, $extra = 0) { if (!is_readable($file)) { return array(); } $file = new SplFileObject($file); // We have to subtract 1 to extract correct lines via SplFileObject. $line = max($lineNumber - 1 - $extra, 0); $result = array(); if (!$file->eof()) { $file->seek($line); for ($i = 0; $i <= ($extra * 2); $i++) { $result[++$line] = trim((string)$file->current(), "\n"); $file->next(); } } return $result; } /** * Take a rule description text and try to decorate/stylize parts of it with HTML. * Based on self::$descHighlightRules config. * * @return string */ protected static function colorize($message) { // Compile final regex, if not done already. if (!self::$compiledHighlightRegex) { $prepared = self::$descHighlightRules; array_walk($prepared, function (&$value, $key) { $value = "(?<{$key}>{$value['regex']})"; }); self::$compiledHighlightRegex = "#(" . implode('|', $prepared) . ")#"; } $rules = self::$descHighlightRules; return preg_replace_callback(self::$compiledHighlightRegex, function ($matches) use ($rules) { // Extract currently matched specification of highlighting (Match groups // are named and we can find out which is not empty.). $definition = array_keys(array_intersect_key($rules, array_filter($matches))); $definition = reset($definition); return "<span class='hlt-info {$definition}'>{$matches[0]}</span>"; }, $message); } /** * Take a file path and return a bit of HTML where the basename is wrapped in styled <span>. * * @return string */ protected static function highlightFile($path) { $file = substr(strrchr($path, "/"), 1); $dir = str_replace($file, '', $path); return $dir . "<span class='path-basename'>" . $file . '</span>'; } /** * Render a pretty informational table and send the HTML to the writer. * * @return void */ protected function writeTable($title, $itemsTitle, $items) { if (!$items) { return; } $writer = $this->getWriter(); $rows = null; // We will need to calculate percentages and whatnot. $max = max($items); $sum = array_sum($items); foreach ($items as $name => $count) { // Calculate chart/bar's percentage width relative to the highest occurring item. $width = $max !== 0 ? $count / $max * 100 : 0; // Avoid division by zero. $bar = sprintf( '<div class="t-bar" style="width: %d%%; opacity: %.2f"></div>', $width, min(0.2 + $width / 100, 1) // Minimum opacity for the bar is 0.2. ); $pct = $sum !== 0 ? sprintf('%.1f', $count / $sum * 100) : '-'; // Avoid division by zero. $rows .= "<tr> <td class='t-cnt'>$count</td> <td class='t-pct'>$pct %</td> <th class='t-n'>{$name}{$bar}</th> </tr>"; } $header = "<thead><tr><th>Count</th><th>%</th><th>$itemsTitle</th></tr></thead>"; $html = "<section><table><caption>$title</caption>{$header}{$rows}</table></section>"; $writer->write(self::reduceWhitespace($html)); } /** * Go through passed violations and count occurrences based on pre-specified conditions. * * @return array */ protected static function sumUpViolations($violations) { $result = array( self::CATEGORY_PRIORITY => array(), self::CATEGORY_NAMESPACE => array(), self::CATEGORY_RULESET => array(), self::CATEGORY_RULE => array(), ); foreach ($violations as $v) { // We use "ref" reference to make things somewhat easier to read. // Also, using a reference to non-existing array index doesn't throw a notice. $namespaceName = $v->getNamespaceName(); if ($namespaceName) { $ref = &$result[self::CATEGORY_NAMESPACE][$namespaceName]; $ref = isset($ref) ? $ref + 1 : 1; } $rule = $v->getRule(); // Friendly priority -> Add a describing word to "just number". $friendlyPriority = self::$priorityTitles[$rule->getPriority()]; $ref = &$result[self::CATEGORY_PRIORITY][$friendlyPriority]; $ref = isset($ref) ? $ref + 1 : 1; $ref = &$result[self::CATEGORY_RULESET][$rule->getRuleSetName()]; $ref = isset($ref) ? $ref + 1 : 1; $ref = &$result[self::CATEGORY_RULE][$rule->getName()]; $ref = isset($ref) ? $ref + 1 : 1; } // Sort numbers in each category from high to low. foreach ($result as &$inner) { arsort($inner); } return $result; } /** * Reduces two and more whitespaces in a row to a single whitespace to conserve space. * * @return string */ protected static function reduceWhitespace($input, $eol = true) { return preg_replace("#\s+#", " ", $input) . ($eol ? PHP_EOL : null); } }