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