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