xref: /plugin/qc/renderer.php (revision b6b2145cc338cb25cf403c26dea71467a23fd215)
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