1<?php 2 3// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps 4 5use dokuwiki\ChangeLog\PageChangeLog; 6use dokuwiki\File\PageResolver; 7use dokuwiki\Utf8\PhpString; 8 9/** 10 * The Renderer 11 */ 12class renderer_plugin_qc extends Doku_Renderer 13{ 14 /** 15 * We store all our data in an array 16 */ 17 public $docArray = [ 18 // raw statistics 19 'header_count' => [0, 0, 0, 0, 0, 0], 20 'header_struct' => [], 21 'linebreak' => 0, 22 'quote_nest' => 0, 23 'quote_count' => 0, 24 'fixme' => 0, 25 'hr' => 0, 26 'formatted' => 0, 27 'created' => 0, 28 'modified' => 0, 29 'changes' => 0, 30 'authors' => [], 31 'internal_links' => 0, 32 'broken_links' => 0, 33 'external_links' => 0, 34 'link_lengths' => [], 35 'chars' => 0, 36 'words' => 0, 37 'score' => 0, 38 // calculated error scores 39 'err' => [ 40 'fixme' => 0, 41 'noh1' => 0, 42 'manyh1' => 0, 43 'headernest' => 0, 44 'manyhr' => 0, 45 'manybr' => 0, 46 'longformat' => 0, 47 'multiformat' => 0, 48 'nobacklink' => 0 49 ], 50 ]; 51 52 protected $quotelevel = 0; 53 protected $formatting = 0; 54 protected $tableopen = false; 55 56 /** @inheritdoc */ 57 public function document_start() 58 { 59 global $ID; 60 $meta = p_get_metadata($ID); 61 62 // get some dates from meta data 63 $this->docArray['created'] = $meta['date']['created']; 64 $this->docArray['modified'] = $meta['date']['modified']; 65 $this->docArray['authors']['*'] = 0; 66 67 // get author info 68 $changelog = new PageChangeLog($ID); 69 $revs = $changelog->getRevisions(0, 10000); //FIXME find a good solution for 'get ALL revisions' 70 $lastChange = $meta['last_change'] ?? null; 71 $revs[] = is_array($lastChange) ? ($lastChange['date'] ?? []) : []; 72 $this->docArray['changes'] = count($revs); 73 foreach ($revs as $rev) { 74 $info = $changelog->getRevisionInfo($rev); 75 if ($info && !empty($info['user'])) { 76 $authorUserCnt = empty($this->docArray['authors'][$info['user']]) 77 ? 0 78 : $this->docArray['authors'][$info['user']]; 79 $this->docArray['authors'][$info['user']] = $authorUserCnt + 1; 80 } else { 81 ++$this->docArray['authors']['*']; 82 } 83 } 84 85 // work on raw text 86 $text = rawWiki($ID); 87 $this->docArray['chars'] = PhpString::strlen($text); 88 $this->docArray['words'] = count(array_filter(preg_split('/[^\w\-_]/u', $text))); 89 } 90 91 92 /** 93 * Here the score is calculated 94 * @inheritdoc 95 */ 96 public function document_end() 97 { 98 global $ID; 99 100 // 2 points for missing backlinks 101 if (ft_backlinks($ID) === []) { 102 $this->docArray['err']['nobacklink'] += 2; 103 } 104 105 // 1 point for each FIXME 106 $this->docArray['err']['fixme'] += $this->docArray['fixme']; 107 108 // 5 points for missing H1 109 if ($this->docArray['header_count'][1] == 0) { 110 $this->docArray['err']['noh1'] += 5; 111 } 112 // 1 point for each H1 too much 113 if ($this->docArray['header_count'][1] > 1) { 114 $this->docArray['err']['manyh1'] += $this->docArray['header_count'][1]; 115 } 116 117 // 1 point for each incorrectly nested headline 118 $cnt = count($this->docArray['header_struct']); 119 for ($i = 1; $i < $cnt; $i++) { 120 if ($this->docArray['header_struct'][$i] - $this->docArray['header_struct'][$i - 1] > 1) { 121 ++$this->docArray['err']['headernest']; 122 } 123 } 124 125 // 1/2 points for deeply nested quotations 126 if ($this->docArray['quote_nest'] > 2) { 127 $this->docArray['err']['deepquote'] = $this->docArray['quote_nest'] / 2; 128 } 129 130 // FIXME points for many quotes? 131 132 // 1/2 points for too many hr 133 if ($this->docArray['hr'] > 2) { 134 $this->docArray['err']['manyhr'] = ($this->docArray['hr'] - 2) / 2; 135 } 136 137 // 1 point for too many line breaks 138 if ($this->docArray['linebreak'] > 2) { 139 $this->docArray['err']['manybr'] = $this->docArray['linebreak'] - 2; 140 } 141 142 // 1 point for single author only 143 if (!$this->getConf('single_author_only') && count($this->docArray['authors']) == 1) { 144 $this->docArray['err']['singleauthor'] = 1; 145 } 146 147 // 1 point for too small document 148 if ($this->docArray['chars'] < 150) { 149 $this->docArray['err']['toosmall'] = 1; 150 } 151 152 // 1 point for too large document 153 if ($this->docArray['chars'] > 100000) { 154 $this->docArray['err']['toolarge'] = 1; 155 } 156 157 // header to text ratio 158 $hc = $this->docArray['header_count'][1] + 159 $this->docArray['header_count'][2] + 160 $this->docArray['header_count'][3] + 161 $this->docArray['header_count'][4] + 162 $this->docArray['header_count'][5]; 163 $hc--; //we expect at least 1 164 if ($hc > 0) { 165 $hr = $this->docArray['chars'] / $hc; 166 167 // 1 point for too many headers 168 if ($hr < 200) { 169 $this->docArray['err']['manyheaders'] = 1; 170 } 171 172 // 1 point for too few headers 173 if ($hr > 2000) { 174 $this->docArray['err']['fewheaders'] = 1; 175 } 176 } 177 178 // 1 point when no link at all 179 if (!$this->docArray['internal_links']) { 180 $this->docArray['err']['nolink'] = 1; 181 } 182 183 // 0.5 for broken links when too many 184 if ($this->docArray['broken_links'] > 2) { 185 $this->docArray['err']['brokenlink'] = $this->docArray['broken_links'] * 0.5; 186 } 187 188 // 2 points for lot's of formatting 189 if ($this->docArray['formatted'] && $this->docArray['chars'] / $this->docArray['formatted'] < 3) { 190 $this->docArray['err']['manyformat'] = 2; 191 } 192 193 // add up all scores 194 foreach ($this->docArray['err'] as $val) $this->docArray['score'] += $val; 195 196 197 //we're done here 198 $this->doc = serialize($this->docArray); 199 } 200 201 /** @inheritdoc */ 202 public function getFormat() 203 { 204 return 'qc'; 205 } 206 207 /** @inheritdoc */ 208 public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') 209 { 210 global $ID; 211 212 $resolver = new PageResolver($ID); 213 $id = $resolver->resolveId($id); 214 $exists = page_exists($id); 215 216 // calculate link width 217 $a = explode(':', getNS($ID)); 218 $b = explode(':', getNS($id)); 219 while (isset($a[0], $b[0]) && $a[0] === $b[0]) { 220 array_shift($a); 221 array_shift($b); 222 } 223 $length = count($a) + count($b); 224 $this->docArray['link_lengths'][] = $length; 225 226 $this->docArray['internal_links']++; 227 if (!$exists) $this->docArray['broken_links']++; 228 } 229 230 /** @inheritdoc */ 231 public function externallink($url, $name = null) 232 { 233 $this->docArray['external_links']++; 234 } 235 236 /** @inheritdoc */ 237 public function header($text, $level, $pos) 238 { 239 $this->docArray['header_count'][$level]++; 240 $this->docArray['header_struct'][] = $level; 241 } 242 243 /** @inheritdoc */ 244 public function smiley($smiley) 245 { 246 if ($smiley == 'FIXME') $this->docArray['fixme']++; 247 } 248 249 /** @inheritdoc */ 250 public function linebreak() 251 { 252 if (!$this->tableopen) { 253 $this->docArray['linebreak']++; 254 } 255 } 256 257 /** @inheritdoc */ 258 public function table_open($maxcols = null, $numrows = null, $pos = null) 259 { 260 $this->tableopen = true; 261 } 262 263 /** @inheritdoc */ 264 public function table_close($pos = null) 265 { 266 $this->tableopen = false; 267 } 268 269 /** @inheritdoc */ 270 public function hr() 271 { 272 $this->docArray['hr']++; 273 } 274 275 /** @inheritdoc */ 276 public function quote_open() 277 { 278 $this->docArray['quote_count']++; 279 $this->quotelevel++; 280 $this->docArray['quote_nest'] = max($this->quotelevel, $this->docArray['quote_nest']); 281 } 282 283 /** @inheritdoc */ 284 public function quote_close() 285 { 286 $this->quotelevel--; 287 } 288 289 /** @inheritdoc */ 290 public function strong_open() 291 { 292 $this->formatting++; 293 } 294 295 /** @inheritdoc */ 296 public function strong_close() 297 { 298 $this->formatting--; 299 } 300 301 /** @inheritdoc */ 302 public function emphasis_open() 303 { 304 $this->formatting++; 305 } 306 307 /** @inheritdoc */ 308 public function emphasis_close() 309 { 310 $this->formatting--; 311 } 312 313 /** @inheritdoc */ 314 public function underline_open() 315 { 316 $this->formatting++; 317 } 318 319 /** @inheritdoc */ 320 public function underline_close() 321 { 322 $this->formatting--; 323 } 324 325 /** @inheritdoc */ 326 public function cdata($text) 327 { 328 if (!$this->formatting) return; 329 330 $len = PhpString::strlen($text); 331 332 // 1 point for formattings longer than 500 chars 333 if ($len > 500) $this->docArray['err']['longformat']++; 334 335 // 1 point for each multiformatting 336 if ($this->formatting > 1) $this->docArray['err']['multiformat'] += 1 * ($this->formatting - 1); 337 338 $this->docArray['formatted'] += $len; 339 } 340} 341