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