1<?php 2/* 3 * This file is part of the php-code-coverage package. 4 * 5 * (c) Sebastian Bergmann <sebastian@phpunit.de> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11namespace SebastianBergmann\CodeCoverage\Report\Html; 12 13use SebastianBergmann\CodeCoverage\Node\AbstractNode; 14use SebastianBergmann\CodeCoverage\Node\File as FileNode; 15use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; 16use SebastianBergmann\Environment\Runtime; 17use SebastianBergmann\Version; 18 19/** 20 * Base class for node renderers. 21 */ 22abstract class Renderer 23{ 24 /** 25 * @var string 26 */ 27 protected $templatePath; 28 29 /** 30 * @var string 31 */ 32 protected $generator; 33 34 /** 35 * @var string 36 */ 37 protected $date; 38 39 /** 40 * @var int 41 */ 42 protected $lowUpperBound; 43 44 /** 45 * @var int 46 */ 47 protected $highLowerBound; 48 49 /** 50 * @var string 51 */ 52 protected $version; 53 54 /** 55 * Constructor. 56 * 57 * @param string $templatePath 58 * @param string $generator 59 * @param string $date 60 * @param int $lowUpperBound 61 * @param int $highLowerBound 62 */ 63 public function __construct($templatePath, $generator, $date, $lowUpperBound, $highLowerBound) 64 { 65 $version = new Version('4.0.8', dirname(dirname(dirname(dirname(__DIR__))))); 66 67 $this->templatePath = $templatePath; 68 $this->generator = $generator; 69 $this->date = $date; 70 $this->lowUpperBound = $lowUpperBound; 71 $this->highLowerBound = $highLowerBound; 72 $this->version = $version->getVersion(); 73 } 74 75 /** 76 * @param \Text_Template $template 77 * @param array $data 78 * 79 * @return string 80 */ 81 protected function renderItemTemplate(\Text_Template $template, array $data) 82 { 83 $numSeparator = ' / '; 84 85 if (isset($data['numClasses']) && $data['numClasses'] > 0) { 86 $classesLevel = $this->getColorLevel($data['testedClassesPercent']); 87 88 $classesNumber = $data['numTestedClasses'] . $numSeparator . 89 $data['numClasses']; 90 91 $classesBar = $this->getCoverageBar( 92 $data['testedClassesPercent'] 93 ); 94 } else { 95 $classesLevel = ''; 96 $classesNumber = '0' . $numSeparator . '0'; 97 $classesBar = ''; 98 $data['testedClassesPercentAsString'] = 'n/a'; 99 } 100 101 if ($data['numMethods'] > 0) { 102 $methodsLevel = $this->getColorLevel($data['testedMethodsPercent']); 103 104 $methodsNumber = $data['numTestedMethods'] . $numSeparator . 105 $data['numMethods']; 106 107 $methodsBar = $this->getCoverageBar( 108 $data['testedMethodsPercent'] 109 ); 110 } else { 111 $methodsLevel = ''; 112 $methodsNumber = '0' . $numSeparator . '0'; 113 $methodsBar = ''; 114 $data['testedMethodsPercentAsString'] = 'n/a'; 115 } 116 117 if ($data['numExecutableLines'] > 0) { 118 $linesLevel = $this->getColorLevel($data['linesExecutedPercent']); 119 120 $linesNumber = $data['numExecutedLines'] . $numSeparator . 121 $data['numExecutableLines']; 122 123 $linesBar = $this->getCoverageBar( 124 $data['linesExecutedPercent'] 125 ); 126 } else { 127 $linesLevel = ''; 128 $linesNumber = '0' . $numSeparator . '0'; 129 $linesBar = ''; 130 $data['linesExecutedPercentAsString'] = 'n/a'; 131 } 132 133 $template->setVar( 134 [ 135 'icon' => isset($data['icon']) ? $data['icon'] : '', 136 'crap' => isset($data['crap']) ? $data['crap'] : '', 137 'name' => $data['name'], 138 'lines_bar' => $linesBar, 139 'lines_executed_percent' => $data['linesExecutedPercentAsString'], 140 'lines_level' => $linesLevel, 141 'lines_number' => $linesNumber, 142 'methods_bar' => $methodsBar, 143 'methods_tested_percent' => $data['testedMethodsPercentAsString'], 144 'methods_level' => $methodsLevel, 145 'methods_number' => $methodsNumber, 146 'classes_bar' => $classesBar, 147 'classes_tested_percent' => isset($data['testedClassesPercentAsString']) ? $data['testedClassesPercentAsString'] : '', 148 'classes_level' => $classesLevel, 149 'classes_number' => $classesNumber 150 ] 151 ); 152 153 return $template->render(); 154 } 155 156 /** 157 * @param \Text_Template $template 158 * @param AbstractNode $node 159 */ 160 protected function setCommonTemplateVariables(\Text_Template $template, AbstractNode $node) 161 { 162 $template->setVar( 163 [ 164 'id' => $node->getId(), 165 'full_path' => $node->getPath(), 166 'path_to_root' => $this->getPathToRoot($node), 167 'breadcrumbs' => $this->getBreadcrumbs($node), 168 'date' => $this->date, 169 'version' => $this->version, 170 'runtime' => $this->getRuntimeString(), 171 'generator' => $this->generator, 172 'low_upper_bound' => $this->lowUpperBound, 173 'high_lower_bound' => $this->highLowerBound 174 ] 175 ); 176 } 177 178 protected function getBreadcrumbs(AbstractNode $node) 179 { 180 $breadcrumbs = ''; 181 $path = $node->getPathAsArray(); 182 $pathToRoot = []; 183 $max = count($path); 184 185 if ($node instanceof FileNode) { 186 $max--; 187 } 188 189 for ($i = 0; $i < $max; $i++) { 190 $pathToRoot[] = str_repeat('../', $i); 191 } 192 193 foreach ($path as $step) { 194 if ($step !== $node) { 195 $breadcrumbs .= $this->getInactiveBreadcrumb( 196 $step, 197 array_pop($pathToRoot) 198 ); 199 } else { 200 $breadcrumbs .= $this->getActiveBreadcrumb($step); 201 } 202 } 203 204 return $breadcrumbs; 205 } 206 207 protected function getActiveBreadcrumb(AbstractNode $node) 208 { 209 $buffer = sprintf( 210 ' <li class="active">%s</li>' . "\n", 211 $node->getName() 212 ); 213 214 if ($node instanceof DirectoryNode) { 215 $buffer .= ' <li>(<a href="dashboard.html">Dashboard</a>)</li>' . "\n"; 216 } 217 218 return $buffer; 219 } 220 221 protected function getInactiveBreadcrumb(AbstractNode $node, $pathToRoot) 222 { 223 return sprintf( 224 ' <li><a href="%sindex.html">%s</a></li>' . "\n", 225 $pathToRoot, 226 $node->getName() 227 ); 228 } 229 230 protected function getPathToRoot(AbstractNode $node) 231 { 232 $id = $node->getId(); 233 $depth = substr_count($id, '/'); 234 235 if ($id != 'index' && 236 $node instanceof DirectoryNode) { 237 $depth++; 238 } 239 240 return str_repeat('../', $depth); 241 } 242 243 protected function getCoverageBar($percent) 244 { 245 $level = $this->getColorLevel($percent); 246 247 $template = new \Text_Template( 248 $this->templatePath . 'coverage_bar.html', 249 '{{', 250 '}}' 251 ); 252 253 $template->setVar(['level' => $level, 'percent' => sprintf('%.2F', $percent)]); 254 255 return $template->render(); 256 } 257 258 /** 259 * @param int $percent 260 * 261 * @return string 262 */ 263 protected function getColorLevel($percent) 264 { 265 if ($percent <= $this->lowUpperBound) { 266 return 'danger'; 267 } elseif ($percent > $this->lowUpperBound && 268 $percent < $this->highLowerBound) { 269 return 'warning'; 270 } else { 271 return 'success'; 272 } 273 } 274 275 /** 276 * @return string 277 */ 278 private function getRuntimeString() 279 { 280 $runtime = new Runtime; 281 282 $buffer = sprintf( 283 '<a href="%s" target="_top">%s %s</a>', 284 $runtime->getVendorUrl(), 285 $runtime->getName(), 286 $runtime->getVersion() 287 ); 288 289 if ($runtime->hasXdebug() && !$runtime->hasPHPDBGCodeCoverage()) { 290 $buffer .= sprintf( 291 ' with <a href="https://xdebug.org/">Xdebug %s</a>', 292 phpversion('xdebug') 293 ); 294 } 295 296 return $buffer; 297 } 298} 299