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\CodeCoverage\Version; 17use SebastianBergmann\Environment\Runtime; 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 $this->templatePath = $templatePath; 66 $this->generator = $generator; 67 $this->date = $date; 68 $this->lowUpperBound = $lowUpperBound; 69 $this->highLowerBound = $highLowerBound; 70 $this->version = Version::id(); 71 } 72 73 /** 74 * @param \Text_Template $template 75 * @param array $data 76 * 77 * @return string 78 */ 79 protected function renderItemTemplate(\Text_Template $template, array $data) 80 { 81 $numSeparator = ' / '; 82 83 if (isset($data['numClasses']) && $data['numClasses'] > 0) { 84 $classesLevel = $this->getColorLevel($data['testedClassesPercent']); 85 86 $classesNumber = $data['numTestedClasses'] . $numSeparator . 87 $data['numClasses']; 88 89 $classesBar = $this->getCoverageBar( 90 $data['testedClassesPercent'] 91 ); 92 } else { 93 $classesLevel = ''; 94 $classesNumber = '0' . $numSeparator . '0'; 95 $classesBar = ''; 96 $data['testedClassesPercentAsString'] = 'n/a'; 97 } 98 99 if ($data['numMethods'] > 0) { 100 $methodsLevel = $this->getColorLevel($data['testedMethodsPercent']); 101 102 $methodsNumber = $data['numTestedMethods'] . $numSeparator . 103 $data['numMethods']; 104 105 $methodsBar = $this->getCoverageBar( 106 $data['testedMethodsPercent'] 107 ); 108 } else { 109 $methodsLevel = ''; 110 $methodsNumber = '0' . $numSeparator . '0'; 111 $methodsBar = ''; 112 $data['testedMethodsPercentAsString'] = 'n/a'; 113 } 114 115 if ($data['numExecutableLines'] > 0) { 116 $linesLevel = $this->getColorLevel($data['linesExecutedPercent']); 117 118 $linesNumber = $data['numExecutedLines'] . $numSeparator . 119 $data['numExecutableLines']; 120 121 $linesBar = $this->getCoverageBar( 122 $data['linesExecutedPercent'] 123 ); 124 } else { 125 $linesLevel = ''; 126 $linesNumber = '0' . $numSeparator . '0'; 127 $linesBar = ''; 128 $data['linesExecutedPercentAsString'] = 'n/a'; 129 } 130 131 $template->setVar( 132 [ 133 'icon' => isset($data['icon']) ? $data['icon'] : '', 134 'crap' => isset($data['crap']) ? $data['crap'] : '', 135 'name' => $data['name'], 136 'lines_bar' => $linesBar, 137 'lines_executed_percent' => $data['linesExecutedPercentAsString'], 138 'lines_level' => $linesLevel, 139 'lines_number' => $linesNumber, 140 'methods_bar' => $methodsBar, 141 'methods_tested_percent' => $data['testedMethodsPercentAsString'], 142 'methods_level' => $methodsLevel, 143 'methods_number' => $methodsNumber, 144 'classes_bar' => $classesBar, 145 'classes_tested_percent' => isset($data['testedClassesPercentAsString']) ? $data['testedClassesPercentAsString'] : '', 146 'classes_level' => $classesLevel, 147 'classes_number' => $classesNumber 148 ] 149 ); 150 151 return $template->render(); 152 } 153 154 /** 155 * @param \Text_Template $template 156 * @param AbstractNode $node 157 */ 158 protected function setCommonTemplateVariables(\Text_Template $template, AbstractNode $node) 159 { 160 $template->setVar( 161 [ 162 'id' => $node->getId(), 163 'full_path' => $node->getPath(), 164 'path_to_root' => $this->getPathToRoot($node), 165 'breadcrumbs' => $this->getBreadcrumbs($node), 166 'date' => $this->date, 167 'version' => $this->version, 168 'runtime' => $this->getRuntimeString(), 169 'generator' => $this->generator, 170 'low_upper_bound' => $this->lowUpperBound, 171 'high_lower_bound' => $this->highLowerBound 172 ] 173 ); 174 } 175 176 protected function getBreadcrumbs(AbstractNode $node) 177 { 178 $breadcrumbs = ''; 179 $path = $node->getPathAsArray(); 180 $pathToRoot = []; 181 $max = count($path); 182 183 if ($node instanceof FileNode) { 184 $max--; 185 } 186 187 for ($i = 0; $i < $max; $i++) { 188 $pathToRoot[] = str_repeat('../', $i); 189 } 190 191 foreach ($path as $step) { 192 if ($step !== $node) { 193 $breadcrumbs .= $this->getInactiveBreadcrumb( 194 $step, 195 array_pop($pathToRoot) 196 ); 197 } else { 198 $breadcrumbs .= $this->getActiveBreadcrumb($step); 199 } 200 } 201 202 return $breadcrumbs; 203 } 204 205 protected function getActiveBreadcrumb(AbstractNode $node) 206 { 207 $buffer = sprintf( 208 ' <li class="active">%s</li>' . "\n", 209 $node->getName() 210 ); 211 212 if ($node instanceof DirectoryNode) { 213 $buffer .= ' <li>(<a href="dashboard.html">Dashboard</a>)</li>' . "\n"; 214 } 215 216 return $buffer; 217 } 218 219 protected function getInactiveBreadcrumb(AbstractNode $node, $pathToRoot) 220 { 221 return sprintf( 222 ' <li><a href="%sindex.html">%s</a></li>' . "\n", 223 $pathToRoot, 224 $node->getName() 225 ); 226 } 227 228 protected function getPathToRoot(AbstractNode $node) 229 { 230 $id = $node->getId(); 231 $depth = substr_count($id, '/'); 232 233 if ($id != 'index' && 234 $node instanceof DirectoryNode) { 235 $depth++; 236 } 237 238 return str_repeat('../', $depth); 239 } 240 241 protected function getCoverageBar($percent) 242 { 243 $level = $this->getColorLevel($percent); 244 245 $template = new \Text_Template( 246 $this->templatePath . 'coverage_bar.html', 247 '{{', 248 '}}' 249 ); 250 251 $template->setVar(['level' => $level, 'percent' => sprintf('%.2F', $percent)]); 252 253 return $template->render(); 254 } 255 256 /** 257 * @param int $percent 258 * 259 * @return string 260 */ 261 protected function getColorLevel($percent) 262 { 263 if ($percent <= $this->lowUpperBound) { 264 return 'danger'; 265 } elseif ($percent > $this->lowUpperBound && 266 $percent < $this->highLowerBound) { 267 return 'warning'; 268 } else { 269 return 'success'; 270 } 271 } 272 273 /** 274 * @return string 275 */ 276 private function getRuntimeString() 277 { 278 $runtime = new Runtime; 279 280 $buffer = sprintf( 281 '<a href="%s" target="_top">%s %s</a>', 282 $runtime->getVendorUrl(), 283 $runtime->getName(), 284 $runtime->getVersion() 285 ); 286 287 if ($runtime->hasXdebug() && !$runtime->hasPHPDBGCodeCoverage()) { 288 $buffer .= sprintf( 289 ' with <a href="https://xdebug.org/">Xdebug %s</a>', 290 phpversion('xdebug') 291 ); 292 } 293 294 return $buffer; 295 } 296} 297