1<?php 2 3use dokuwiki\Utf8\PhpString; 4 5/** 6 * The Renderer 7 */ 8class renderer_plugin_qc extends Doku_Renderer 9{ 10 /** 11 * We store all our data in an array 12 */ 13 public $docArray = array( 14 // raw statistics 15 'header_count' => array(0, 0, 0, 0, 0, 0), 16 'header_struct' => array(), 17 'linebreak' => 0, 18 'quote_nest' => 0, 19 'quote_count' => 0, 20 'fixme' => 0, 21 'hr' => 0, 22 'formatted' => 0, 23 24 'created' => 0, 25 'modified' => 0, 26 'changes' => 0, 27 'authors' => array(), 28 29 'internal_links' => 0, 30 'broken_links' => 0, 31 'external_links' => 0, 32 'link_lengths' => array(), 33 34 'chars' => 0, 35 'words' => 0, 36 37 'score' => 0, 38 39 // calculated error scores 40 'err' => array( 41 'fixme' => 0, 42 'noh1' => 0, 43 'manyh1' => 0, 44 'headernest' => 0, 45 'manyhr' => 0, 46 'manybr' => 0, 47 'longformat' => 0, 48 'multiformat' => 0, 49 ), 50 ); 51 52 protected $quotelevel = 0; 53 protected $formatting = 0; 54 protected $tableopen = false; 55 56 /** @inheritdoc */ 57 public function document_start() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 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 \dokuwiki\ChangeLog\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 ? $this->docArray['authors'][$info['user']] 77 : 0; 78 $this->docArray['authors'][$info['user']] = $authorUserCnt + 1; 79 } else { 80 $this->docArray['authors']['*'] += 1; 81 } 82 } 83 84 // work on raw text 85 $text = rawWiki($ID); 86 $this->docArray['chars'] = utf8_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() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 96 { 97 global $ID; 98 99 // 2 points for missing backlinks 100 if (!count(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'][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'] += 1; 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 $err => $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 \dokuwiki\File\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) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 258 { 259 $this->tableopen = true; 260 } 261 262 /** @inheritdoc */ 263 public function table_close($pos = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 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() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 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() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 284 { 285 $this->quotelevel--; 286 } 287 288 /** @inheritdoc */ 289 public function strong_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 290 { 291 $this->formatting++; 292 } 293 294 /** @inheritdoc */ 295 public function strong_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 296 { 297 $this->formatting--; 298 } 299 300 /** @inheritdoc */ 301 public function emphasis_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 302 { 303 $this->formatting++; 304 } 305 306 /** @inheritdoc */ 307 public function emphasis_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 308 { 309 $this->formatting--; 310 } 311 312 /** @inheritdoc */ 313 public function underline_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 314 { 315 $this->formatting++; 316 } 317 318 /** @inheritdoc */ 319 public function underline_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 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