xref: /plugin/qc/renderer.php (revision 2fc45e0c1b2076ea358377649512960c5d3d694a)
18fce80b1SAndreas Gohr<?php
276bbc49cSAnna Dabrowska
3*2fc45e0cSsplitbrainuse dokuwiki\ChangeLog\PageChangeLog;
4*2fc45e0cSsplitbrainuse dokuwiki\File\PageResolver;
5*2fc45e0cSsplitbrain
6*2fc45e0cSsplitbrainuse function dokuwiki\Utf8\PhpString::strlen;
7*2fc45e0cSsplitbrain
88ae469bfSAndreas Gohruse dokuwiki\Utf8\PhpString;
98ae469bfSAndreas Gohr
108fce80b1SAndreas Gohr/**
118fce80b1SAndreas Gohr * The Renderer
128fce80b1SAndreas Gohr */
1376bbc49cSAnna Dabrowskaclass renderer_plugin_qc extends Doku_Renderer
1476bbc49cSAnna Dabrowska{
158fce80b1SAndreas Gohr    /**
168fce80b1SAndreas Gohr     * We store all our data in an array
178fce80b1SAndreas Gohr     */
18*2fc45e0cSsplitbrain    public $docArray = [
198fce80b1SAndreas Gohr        // raw statistics
20*2fc45e0cSsplitbrain        'header_count' => [0, 0, 0, 0, 0, 0],
21*2fc45e0cSsplitbrain        'header_struct' => [],
228fce80b1SAndreas Gohr        'linebreak' => 0,
238fce80b1SAndreas Gohr        'quote_nest' => 0,
248fce80b1SAndreas Gohr        'quote_count' => 0,
258fce80b1SAndreas Gohr        'fixme' => 0,
268fce80b1SAndreas Gohr        'hr' => 0,
27d723b313SAndreas Gohr        'formatted' => 0,
284ea373cdSAndreas Gohr        'created' => 0,
294ea373cdSAndreas Gohr        'modified' => 0,
304ea373cdSAndreas Gohr        'changes' => 0,
31*2fc45e0cSsplitbrain        'authors' => [],
32d723b313SAndreas Gohr        'internal_links' => 0,
33d723b313SAndreas Gohr        'broken_links' => 0,
34d723b313SAndreas Gohr        'external_links' => 0,
35*2fc45e0cSsplitbrain        'link_lengths' => [],
364ea373cdSAndreas Gohr        'chars' => 0,
374ea373cdSAndreas Gohr        'words' => 0,
389068e431SAndreas Gohr        'score' => 0,
398fce80b1SAndreas Gohr        // calculated error scores
40*2fc45e0cSsplitbrain        'err' => ['fixme' => 0, 'noh1' => 0, 'manyh1' => 0, 'headernest' => 0, 'manyhr' => 0, 'manybr' => 0, 'longformat' => 0, 'multiformat' => 0],
41*2fc45e0cSsplitbrain    ];
428fce80b1SAndreas Gohr
437db0beafSAnna Dabrowska    protected $quotelevel = 0;
447db0beafSAnna Dabrowska    protected $formatting = 0;
457db0beafSAnna Dabrowska    protected $tableopen = false;
468fce80b1SAndreas Gohr
478ae469bfSAndreas Gohr    /** @inheritdoc */
48293182bbSAnna Dabrowska    public function document_start() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
4976bbc49cSAnna Dabrowska    {
504ea373cdSAndreas Gohr        global $ID;
514ea373cdSAndreas Gohr        $meta = p_get_metadata($ID);
524ea373cdSAndreas Gohr
534ea373cdSAndreas Gohr        // get some dates from meta data
547db0beafSAnna Dabrowska        $this->docArray['created'] = $meta['date']['created'];
557db0beafSAnna Dabrowska        $this->docArray['modified'] = $meta['date']['modified'];
568fdbbd20SAnna Dabrowska        $this->docArray['authors']['*'] = 0;
574ea373cdSAndreas Gohr
584ea373cdSAndreas Gohr        // get author info
59*2fc45e0cSsplitbrain        $changelog = new PageChangeLog($ID);
605929c83eSMichael Große        $revs = $changelog->getRevisions(0, 10000); //FIXME find a good solution for 'get ALL revisions'
618ae469bfSAndreas Gohr        $revs[] = $meta['last_change']['date'];
627db0beafSAnna Dabrowska        $this->docArray['changes'] = count($revs);
634ea373cdSAndreas Gohr        foreach ($revs as $rev) {
64088a1fe7SMichael Grosse            $info = $changelog->getRevisionInfo($rev);
658ae469bfSAndreas Gohr            if ($info && !empty($info['user'])) {
66*2fc45e0cSsplitbrain                $authorUserCnt = empty($this->docArray['authors'][$info['user']])
67*2fc45e0cSsplitbrain                    ? 0
68*2fc45e0cSsplitbrain                    : $this->docArray['authors'][$info['user']];
698fdbbd20SAnna Dabrowska                $this->docArray['authors'][$info['user']] = $authorUserCnt + 1;
704ea373cdSAndreas Gohr            } else {
71*2fc45e0cSsplitbrain                ++$this->docArray['authors']['*'];
724ea373cdSAndreas Gohr            }
734ea373cdSAndreas Gohr        }
744ea373cdSAndreas Gohr
754ea373cdSAndreas Gohr        // work on raw text
764ea373cdSAndreas Gohr        $text = rawWiki($ID);
77*2fc45e0cSsplitbrain        $this->docArray['chars'] = PhpString::strlen($text);
787db0beafSAnna Dabrowska        $this->docArray['words'] = count(array_filter(preg_split('/[^\w\-_]/u', $text)));
794ea373cdSAndreas Gohr    }
804ea373cdSAndreas Gohr
814ea373cdSAndreas Gohr
828fce80b1SAndreas Gohr    /**
838fce80b1SAndreas Gohr     * Here the score is calculated
848ae469bfSAndreas Gohr     * @inheritdoc
858fce80b1SAndreas Gohr     */
86293182bbSAnna Dabrowska    public function document_end() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
8776bbc49cSAnna Dabrowska    {
88e348ef7cSAndreas Gohr        global $ID;
89e348ef7cSAndreas Gohr
90e348ef7cSAndreas Gohr        // 2 points for missing backlinks
91*2fc45e0cSsplitbrain        if (ft_backlinks($ID) === []) {
927db0beafSAnna Dabrowska            $this->docArray['err']['nobacklink'] += 2;
93e348ef7cSAndreas Gohr        }
948fce80b1SAndreas Gohr
958fce80b1SAndreas Gohr        // 1 point for each FIXME
967db0beafSAnna Dabrowska        $this->docArray['err']['fixme'] += $this->docArray['fixme'];
978fce80b1SAndreas Gohr
988fce80b1SAndreas Gohr        // 5 points for missing H1
997db0beafSAnna Dabrowska        if ($this->docArray['header_count'][1] == 0) {
1007db0beafSAnna Dabrowska            $this->docArray['err']['noh1'] += 5;
1018fce80b1SAndreas Gohr        }
1028fce80b1SAndreas Gohr        // 1 point for each H1 too much
1037db0beafSAnna Dabrowska        if ($this->docArray['header_count'][1] > 1) {
1047db0beafSAnna Dabrowska            $this->docArray['err']['manyh1'] += $this->docArray['header'][1];
1058fce80b1SAndreas Gohr        }
1068fce80b1SAndreas Gohr
1078fce80b1SAndreas Gohr        // 1 point for each incorrectly nested headline
1087db0beafSAnna Dabrowska        $cnt = count($this->docArray['header_struct']);
1098fce80b1SAndreas Gohr        for ($i = 1; $i < $cnt; $i++) {
1107db0beafSAnna Dabrowska            if ($this->docArray['header_struct'][$i] - $this->docArray['header_struct'][$i - 1] > 1) {
111*2fc45e0cSsplitbrain                ++$this->docArray['err']['headernest'];
1128fce80b1SAndreas Gohr            }
1138fce80b1SAndreas Gohr        }
1148fce80b1SAndreas Gohr
1158fce80b1SAndreas Gohr        // 1/2 points for deeply nested quotations
1167db0beafSAnna Dabrowska        if ($this->docArray['quote_nest'] > 2) {
117af8294b9SAndreas Gohr            $this->docArray['err']['deepquote'] = $this->docArray['quote_nest'] / 2;
1188fce80b1SAndreas Gohr        }
1198fce80b1SAndreas Gohr
1208fce80b1SAndreas Gohr        // FIXME points for many quotes?
1218fce80b1SAndreas Gohr
1228fce80b1SAndreas Gohr        // 1/2 points for too many hr
1237db0beafSAnna Dabrowska        if ($this->docArray['hr'] > 2) {
1247db0beafSAnna Dabrowska            $this->docArray['err']['manyhr'] = ($this->docArray['hr'] - 2) / 2;
1258fce80b1SAndreas Gohr        }
1268fce80b1SAndreas Gohr
1278fce80b1SAndreas Gohr        // 1 point for too many line breaks
1287db0beafSAnna Dabrowska        if ($this->docArray['linebreak'] > 2) {
1297db0beafSAnna Dabrowska            $this->docArray['err']['manybr'] = $this->docArray['linebreak'] - 2;
1308fce80b1SAndreas Gohr        }
1318fce80b1SAndreas Gohr
1324ea373cdSAndreas Gohr        // 1 point for single author only
1337db0beafSAnna Dabrowska        if (!$this->getConf('single_author_only') && count($this->docArray['authors']) == 1) {
1347db0beafSAnna Dabrowska            $this->docArray['err']['singleauthor'] = 1;
1354ea373cdSAndreas Gohr        }
1364ea373cdSAndreas Gohr
137d723b313SAndreas Gohr        // 1 point for too small document
1387db0beafSAnna Dabrowska        if ($this->docArray['chars'] < 150) {
1397db0beafSAnna Dabrowska            $this->docArray['err']['toosmall'] = 1;
140d723b313SAndreas Gohr        }
141d723b313SAndreas Gohr
142d723b313SAndreas Gohr        // 1 point for too large document
1437db0beafSAnna Dabrowska        if ($this->docArray['chars'] > 100000) {
1447db0beafSAnna Dabrowska            $this->docArray['err']['toolarge'] = 1;
145d723b313SAndreas Gohr        }
146d723b313SAndreas Gohr
147d723b313SAndreas Gohr        // header to text ratio
1487db0beafSAnna Dabrowska        $hc = $this->docArray['header_count'][1] +
1497db0beafSAnna Dabrowska            $this->docArray['header_count'][2] +
1507db0beafSAnna Dabrowska            $this->docArray['header_count'][3] +
1517db0beafSAnna Dabrowska            $this->docArray['header_count'][4] +
1527db0beafSAnna Dabrowska            $this->docArray['header_count'][5];
15313abf7ccSAndreas Gohr        $hc--; //we expect at least 1
15413abf7ccSAndreas Gohr        if ($hc > 0) {
1557db0beafSAnna Dabrowska            $hr = $this->docArray['chars'] / $hc;
156d723b313SAndreas Gohr
157d723b313SAndreas Gohr            // 1 point for too many headers
158d723b313SAndreas Gohr            if ($hr < 200) {
1597db0beafSAnna Dabrowska                $this->docArray['err']['manyheaders'] = 1;
160d723b313SAndreas Gohr            }
161d723b313SAndreas Gohr
162d723b313SAndreas Gohr            // 1 point for too few headers
1631b085f03SAndreas Gohr            if ($hr > 2000) {
1647db0beafSAnna Dabrowska                $this->docArray['err']['fewheaders'] = 1;
165d723b313SAndreas Gohr            }
166d723b313SAndreas Gohr        }
167d723b313SAndreas Gohr
168d723b313SAndreas Gohr        // 1 point when no link at all
1697db0beafSAnna Dabrowska        if (!$this->docArray['internal_links']) {
1707db0beafSAnna Dabrowska            $this->docArray['err']['nolink'] = 1;
171d723b313SAndreas Gohr        }
172d723b313SAndreas Gohr
173d723b313SAndreas Gohr        // 0.5 for broken links when too many
1747db0beafSAnna Dabrowska        if ($this->docArray['broken_links'] > 2) {
1757db0beafSAnna Dabrowska            $this->docArray['err']['brokenlink'] = $this->docArray['broken_links'] * 0.5;
176d723b313SAndreas Gohr        }
177d723b313SAndreas Gohr
178d723b313SAndreas Gohr        // 2 points for lot's of formatting
1797db0beafSAnna Dabrowska        if ($this->docArray['formatted'] && $this->docArray['chars'] / $this->docArray['formatted'] < 3) {
1807db0beafSAnna Dabrowska            $this->docArray['err']['manyformat'] = 2;
181d723b313SAndreas Gohr        }
182d723b313SAndreas Gohr
1839068e431SAndreas Gohr        // add up all scores
184*2fc45e0cSsplitbrain        foreach ($this->docArray['err'] as $val) $this->docArray['score'] += $val;
1859068e431SAndreas Gohr
1864ea373cdSAndreas Gohr
1878fce80b1SAndreas Gohr        //we're done here
1887db0beafSAnna Dabrowska        $this->doc = serialize($this->docArray);
1898fce80b1SAndreas Gohr    }
1908fce80b1SAndreas Gohr
1918ae469bfSAndreas Gohr    /** @inheritdoc */
19276bbc49cSAnna Dabrowska    public function getFormat()
19376bbc49cSAnna Dabrowska    {
1948fce80b1SAndreas Gohr        return 'qc';
1958fce80b1SAndreas Gohr    }
1968fce80b1SAndreas Gohr
1978ae469bfSAndreas Gohr    /** @inheritdoc */
19876bbc49cSAnna Dabrowska    public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content')
19976bbc49cSAnna Dabrowska    {
200d723b313SAndreas Gohr        global $ID;
2018ae469bfSAndreas Gohr
202*2fc45e0cSsplitbrain        $resolver = new PageResolver($ID);
2038ae469bfSAndreas Gohr        $id = $resolver->resolveId($id);
2048ae469bfSAndreas Gohr        $exists = page_exists($id);
205d723b313SAndreas Gohr
2068d7cf088SAndreas Gohr        // calculate link width
207d78155a4SAndreas Gohr        $a = explode(':', getNS($ID));
208d78155a4SAndreas Gohr        $b = explode(':', getNS($id));
209*2fc45e0cSsplitbrain        while (isset($a[0]) && $a[0] === $b[0]) {
2108d7cf088SAndreas Gohr            array_shift($a);
2118d7cf088SAndreas Gohr            array_shift($b);
2128d7cf088SAndreas Gohr        }
213d78155a4SAndreas Gohr        $length = count($a) + count($b);
2147db0beafSAnna Dabrowska        $this->docArray['link_lengths'][] = $length;
2158d7cf088SAndreas Gohr
2167db0beafSAnna Dabrowska        $this->docArray['internal_links']++;
2177db0beafSAnna Dabrowska        if (!$exists) $this->docArray['broken_links']++;
2180476d180SAndreas Gohr    }
2190476d180SAndreas Gohr
2208ae469bfSAndreas Gohr    /** @inheritdoc */
22176bbc49cSAnna Dabrowska    public function externallink($url, $name = null)
22276bbc49cSAnna Dabrowska    {
2237db0beafSAnna Dabrowska        $this->docArray['external_links']++;
224d723b313SAndreas Gohr    }
2258fce80b1SAndreas Gohr
2268ae469bfSAndreas Gohr    /** @inheritdoc */
22776bbc49cSAnna Dabrowska    public function header($text, $level, $pos)
22876bbc49cSAnna Dabrowska    {
2297db0beafSAnna Dabrowska        $this->docArray['header_count'][$level]++;
2307db0beafSAnna Dabrowska        $this->docArray['header_struct'][] = $level;
2318fce80b1SAndreas Gohr    }
2328fce80b1SAndreas Gohr
2338ae469bfSAndreas Gohr    /** @inheritdoc */
23476bbc49cSAnna Dabrowska    public function smiley($smiley)
23576bbc49cSAnna Dabrowska    {
2367db0beafSAnna Dabrowska        if ($smiley == 'FIXME') $this->docArray['fixme']++;
2378fce80b1SAndreas Gohr    }
2388fce80b1SAndreas Gohr
2398ae469bfSAndreas Gohr    /** @inheritdoc */
24076bbc49cSAnna Dabrowska    public function linebreak()
24176bbc49cSAnna Dabrowska    {
2426afc1841Sthesunrise1983        if (!$this->tableopen) {
2437db0beafSAnna Dabrowska            $this->docArray['linebreak']++;
2448fce80b1SAndreas Gohr        }
2456afc1841Sthesunrise1983    }
2466afc1841Sthesunrise1983
2478ae469bfSAndreas Gohr    /** @inheritdoc */
248293182bbSAnna Dabrowska    public function table_open($maxcols = null, $numrows = null, $pos = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
24976bbc49cSAnna Dabrowska    {
2506afc1841Sthesunrise1983        $this->tableopen = true;
2516afc1841Sthesunrise1983    }
2526afc1841Sthesunrise1983
2538ae469bfSAndreas Gohr    /** @inheritdoc */
254293182bbSAnna Dabrowska    public function table_close($pos = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
25576bbc49cSAnna Dabrowska    {
2566afc1841Sthesunrise1983        $this->tableopen = false;
2576afc1841Sthesunrise1983    }
2588fce80b1SAndreas Gohr
2598ae469bfSAndreas Gohr    /** @inheritdoc */
26076bbc49cSAnna Dabrowska    public function hr()
26176bbc49cSAnna Dabrowska    {
2627db0beafSAnna Dabrowska        $this->docArray['hr']++;
2638fce80b1SAndreas Gohr    }
2648fce80b1SAndreas Gohr
2658ae469bfSAndreas Gohr    /** @inheritdoc */
266293182bbSAnna Dabrowska    public function quote_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
26776bbc49cSAnna Dabrowska    {
2687db0beafSAnna Dabrowska        $this->docArray['quote_count']++;
2698fce80b1SAndreas Gohr        $this->quotelevel++;
2707db0beafSAnna Dabrowska        $this->docArray['quote_nest'] = max($this->quotelevel, $this->docArray['quote_nest']);
2718fce80b1SAndreas Gohr    }
2728fce80b1SAndreas Gohr
2738ae469bfSAndreas Gohr    /** @inheritdoc */
274293182bbSAnna Dabrowska    public function quote_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
27576bbc49cSAnna Dabrowska    {
2768fce80b1SAndreas Gohr        $this->quotelevel--;
2778fce80b1SAndreas Gohr    }
2788fce80b1SAndreas Gohr
2798ae469bfSAndreas Gohr    /** @inheritdoc */
280293182bbSAnna Dabrowska    public function strong_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
28176bbc49cSAnna Dabrowska    {
282d723b313SAndreas Gohr        $this->formatting++;
283d723b313SAndreas Gohr    }
2848fce80b1SAndreas Gohr
2858ae469bfSAndreas Gohr    /** @inheritdoc */
286293182bbSAnna Dabrowska    public function strong_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
28776bbc49cSAnna Dabrowska    {
288d723b313SAndreas Gohr        $this->formatting--;
289d723b313SAndreas Gohr    }
290d723b313SAndreas Gohr
2918ae469bfSAndreas Gohr    /** @inheritdoc */
292293182bbSAnna Dabrowska    public function emphasis_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
29376bbc49cSAnna Dabrowska    {
294d723b313SAndreas Gohr        $this->formatting++;
295d723b313SAndreas Gohr    }
296d723b313SAndreas Gohr
2978ae469bfSAndreas Gohr    /** @inheritdoc */
298293182bbSAnna Dabrowska    public function emphasis_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
29976bbc49cSAnna Dabrowska    {
300d723b313SAndreas Gohr        $this->formatting--;
301d723b313SAndreas Gohr    }
302d723b313SAndreas Gohr
3038ae469bfSAndreas Gohr    /** @inheritdoc */
304293182bbSAnna Dabrowska    public function underline_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
30576bbc49cSAnna Dabrowska    {
306d723b313SAndreas Gohr        $this->formatting++;
307d723b313SAndreas Gohr    }
308d723b313SAndreas Gohr
3098ae469bfSAndreas Gohr    /** @inheritdoc */
310293182bbSAnna Dabrowska    public function underline_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
31176bbc49cSAnna Dabrowska    {
312d723b313SAndreas Gohr        $this->formatting--;
313d723b313SAndreas Gohr    }
314d723b313SAndreas Gohr
3158ae469bfSAndreas Gohr    /** @inheritdoc */
31676bbc49cSAnna Dabrowska    public function cdata($text)
31776bbc49cSAnna Dabrowska    {
318d723b313SAndreas Gohr        if (!$this->formatting) return;
319d723b313SAndreas Gohr
3208ae469bfSAndreas Gohr        $len = PhpString::strlen($text);
321d723b313SAndreas Gohr
322d723b313SAndreas Gohr        // 1 point for formattings longer than 500 chars
3237db0beafSAnna Dabrowska        if ($len > 500) $this->docArray['err']['longformat']++;
324d723b313SAndreas Gohr
3254bda998cSAndreas Gohr        // 1 point for each multiformatting
3267db0beafSAnna Dabrowska        if ($this->formatting > 1) $this->docArray['err']['multiformat'] += 1 * ($this->formatting - 1);
3274bda998cSAndreas Gohr
3287db0beafSAnna Dabrowska        $this->docArray['formatted'] += $len;
329d723b313SAndreas Gohr    }
3308fce80b1SAndreas Gohr}
331