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