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