xref: /plugin/qc/renderer.php (revision fd6384fc8f369d93f23b9159459d0b315bb4f0e6)
18fce80b1SAndreas Gohr<?php
23c4e5ff0Ssplitbrain
3c8d45e58SAndreas Gohr// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
476bbc49cSAnna Dabrowska
52fc45e0cSsplitbrainuse dokuwiki\ChangeLog\PageChangeLog;
62fc45e0cSsplitbrainuse dokuwiki\File\PageResolver;
78ae469bfSAndreas Gohruse dokuwiki\Utf8\PhpString;
88ae469bfSAndreas Gohr
98fce80b1SAndreas Gohr/**
108fce80b1SAndreas Gohr * The Renderer
118fce80b1SAndreas Gohr */
1276bbc49cSAnna Dabrowskaclass renderer_plugin_qc extends Doku_Renderer
1376bbc49cSAnna Dabrowska{
148fce80b1SAndreas Gohr    /**
158fce80b1SAndreas Gohr     * We store all our data in an array
168fce80b1SAndreas Gohr     */
172fc45e0cSsplitbrain    public $docArray = [
188fce80b1SAndreas Gohr        // raw statistics
192fc45e0cSsplitbrain        'header_count' => [0, 0, 0, 0, 0, 0],
202fc45e0cSsplitbrain        'header_struct' => [],
218fce80b1SAndreas Gohr        'linebreak' => 0,
228fce80b1SAndreas Gohr        'quote_nest' => 0,
238fce80b1SAndreas Gohr        'quote_count' => 0,
248fce80b1SAndreas Gohr        'fixme' => 0,
258fce80b1SAndreas Gohr        'hr' => 0,
26d723b313SAndreas Gohr        'formatted' => 0,
274ea373cdSAndreas Gohr        'created' => 0,
284ea373cdSAndreas Gohr        'modified' => 0,
294ea373cdSAndreas Gohr        'changes' => 0,
302fc45e0cSsplitbrain        'authors' => [],
31d723b313SAndreas Gohr        'internal_links' => 0,
32d723b313SAndreas Gohr        'broken_links' => 0,
33d723b313SAndreas Gohr        'external_links' => 0,
342fc45e0cSsplitbrain        'link_lengths' => [],
354ea373cdSAndreas Gohr        'chars' => 0,
364ea373cdSAndreas Gohr        'words' => 0,
379068e431SAndreas Gohr        'score' => 0,
388fce80b1SAndreas Gohr        // calculated error scores
39c8d45e58SAndreas Gohr        'err' => [
40c8d45e58SAndreas Gohr            'fixme' => 0,
41c8d45e58SAndreas Gohr            'noh1' => 0,
42c8d45e58SAndreas Gohr            'manyh1' => 0,
43c8d45e58SAndreas Gohr            'headernest' => 0,
44c8d45e58SAndreas Gohr            'manyhr' => 0,
45c8d45e58SAndreas Gohr            'manybr' => 0,
46c8d45e58SAndreas Gohr            'longformat' => 0,
4748df69a5Sribsey            'multiformat' => 0,
4848df69a5Sribsey            'nobacklink' => 0
49c8d45e58SAndreas Gohr        ],
502fc45e0cSsplitbrain    ];
518fce80b1SAndreas Gohr
527db0beafSAnna Dabrowska    protected $quotelevel = 0;
537db0beafSAnna Dabrowska    protected $formatting = 0;
547db0beafSAnna Dabrowska    protected $tableopen = false;
558fce80b1SAndreas Gohr
568ae469bfSAndreas Gohr    /** @inheritdoc */
57c8d45e58SAndreas Gohr    public function document_start()
5876bbc49cSAnna Dabrowska    {
594ea373cdSAndreas Gohr        global $ID;
604ea373cdSAndreas Gohr        $meta = p_get_metadata($ID);
614ea373cdSAndreas Gohr
624ea373cdSAndreas Gohr        // get some dates from meta data
637db0beafSAnna Dabrowska        $this->docArray['created'] = $meta['date']['created'];
647db0beafSAnna Dabrowska        $this->docArray['modified'] = $meta['date']['modified'];
658fdbbd20SAnna Dabrowska        $this->docArray['authors']['*'] = 0;
664ea373cdSAndreas Gohr
674ea373cdSAndreas Gohr        // get author info
682fc45e0cSsplitbrain        $changelog = new PageChangeLog($ID);
695929c83eSMichael Große        $revs = $changelog->getRevisions(0, 10000); //FIXME find a good solution for 'get ALL revisions'
70*fd6384fcSribsey        $lastChange = $meta['last_change'] ?? null;
71*fd6384fcSribsey        $revs[] = is_array($lastChange) ? ($lastChange['date'] ?? []) : [];
727db0beafSAnna Dabrowska        $this->docArray['changes'] = count($revs);
734ea373cdSAndreas Gohr        foreach ($revs as $rev) {
74088a1fe7SMichael Grosse            $info = $changelog->getRevisionInfo($rev);
758ae469bfSAndreas Gohr            if ($info && !empty($info['user'])) {
762fc45e0cSsplitbrain                $authorUserCnt = empty($this->docArray['authors'][$info['user']])
772fc45e0cSsplitbrain                    ? 0
782fc45e0cSsplitbrain                    : $this->docArray['authors'][$info['user']];
798fdbbd20SAnna Dabrowska                $this->docArray['authors'][$info['user']] = $authorUserCnt + 1;
804ea373cdSAndreas Gohr            } else {
812fc45e0cSsplitbrain                ++$this->docArray['authors']['*'];
824ea373cdSAndreas Gohr            }
834ea373cdSAndreas Gohr        }
844ea373cdSAndreas Gohr
854ea373cdSAndreas Gohr        // work on raw text
864ea373cdSAndreas Gohr        $text = rawWiki($ID);
872fc45e0cSsplitbrain        $this->docArray['chars'] = PhpString::strlen($text);
887db0beafSAnna Dabrowska        $this->docArray['words'] = count(array_filter(preg_split('/[^\w\-_]/u', $text)));
894ea373cdSAndreas Gohr    }
904ea373cdSAndreas Gohr
914ea373cdSAndreas Gohr
928fce80b1SAndreas Gohr    /**
938fce80b1SAndreas Gohr     * Here the score is calculated
948ae469bfSAndreas Gohr     * @inheritdoc
958fce80b1SAndreas Gohr     */
96c8d45e58SAndreas Gohr    public function document_end()
9776bbc49cSAnna Dabrowska    {
98e348ef7cSAndreas Gohr        global $ID;
99e348ef7cSAndreas Gohr
100e348ef7cSAndreas Gohr        // 2 points for missing backlinks
1012fc45e0cSsplitbrain        if (ft_backlinks($ID) === []) {
1027db0beafSAnna Dabrowska            $this->docArray['err']['nobacklink'] += 2;
103e348ef7cSAndreas Gohr        }
1048fce80b1SAndreas Gohr
1058fce80b1SAndreas Gohr        // 1 point for each FIXME
1067db0beafSAnna Dabrowska        $this->docArray['err']['fixme'] += $this->docArray['fixme'];
1078fce80b1SAndreas Gohr
1088fce80b1SAndreas Gohr        // 5 points for missing H1
1097db0beafSAnna Dabrowska        if ($this->docArray['header_count'][1] == 0) {
1107db0beafSAnna Dabrowska            $this->docArray['err']['noh1'] += 5;
1118fce80b1SAndreas Gohr        }
1128fce80b1SAndreas Gohr        // 1 point for each H1 too much
1137db0beafSAnna Dabrowska        if ($this->docArray['header_count'][1] > 1) {
1144aed63edSZweihorn            $this->docArray['err']['manyh1'] += $this->docArray['header_count'][1];
1158fce80b1SAndreas Gohr        }
1168fce80b1SAndreas Gohr
1178fce80b1SAndreas Gohr        // 1 point for each incorrectly nested headline
1187db0beafSAnna Dabrowska        $cnt = count($this->docArray['header_struct']);
1198fce80b1SAndreas Gohr        for ($i = 1; $i < $cnt; $i++) {
1207db0beafSAnna Dabrowska            if ($this->docArray['header_struct'][$i] - $this->docArray['header_struct'][$i - 1] > 1) {
1212fc45e0cSsplitbrain                ++$this->docArray['err']['headernest'];
1228fce80b1SAndreas Gohr            }
1238fce80b1SAndreas Gohr        }
1248fce80b1SAndreas Gohr
1258fce80b1SAndreas Gohr        // 1/2 points for deeply nested quotations
1267db0beafSAnna Dabrowska        if ($this->docArray['quote_nest'] > 2) {
127af8294b9SAndreas Gohr            $this->docArray['err']['deepquote'] = $this->docArray['quote_nest'] / 2;
1288fce80b1SAndreas Gohr        }
1298fce80b1SAndreas Gohr
1308fce80b1SAndreas Gohr        // FIXME points for many quotes?
1318fce80b1SAndreas Gohr
1328fce80b1SAndreas Gohr        // 1/2 points for too many hr
1337db0beafSAnna Dabrowska        if ($this->docArray['hr'] > 2) {
1347db0beafSAnna Dabrowska            $this->docArray['err']['manyhr'] = ($this->docArray['hr'] - 2) / 2;
1358fce80b1SAndreas Gohr        }
1368fce80b1SAndreas Gohr
1378fce80b1SAndreas Gohr        // 1 point for too many line breaks
1387db0beafSAnna Dabrowska        if ($this->docArray['linebreak'] > 2) {
1397db0beafSAnna Dabrowska            $this->docArray['err']['manybr'] = $this->docArray['linebreak'] - 2;
1408fce80b1SAndreas Gohr        }
1418fce80b1SAndreas Gohr
1424ea373cdSAndreas Gohr        // 1 point for single author only
1437db0beafSAnna Dabrowska        if (!$this->getConf('single_author_only') && count($this->docArray['authors']) == 1) {
1447db0beafSAnna Dabrowska            $this->docArray['err']['singleauthor'] = 1;
1454ea373cdSAndreas Gohr        }
1464ea373cdSAndreas Gohr
147d723b313SAndreas Gohr        // 1 point for too small document
1487db0beafSAnna Dabrowska        if ($this->docArray['chars'] < 150) {
1497db0beafSAnna Dabrowska            $this->docArray['err']['toosmall'] = 1;
150d723b313SAndreas Gohr        }
151d723b313SAndreas Gohr
152d723b313SAndreas Gohr        // 1 point for too large document
1537db0beafSAnna Dabrowska        if ($this->docArray['chars'] > 100000) {
1547db0beafSAnna Dabrowska            $this->docArray['err']['toolarge'] = 1;
155d723b313SAndreas Gohr        }
156d723b313SAndreas Gohr
157d723b313SAndreas Gohr        // header to text ratio
1587db0beafSAnna Dabrowska        $hc = $this->docArray['header_count'][1] +
1597db0beafSAnna Dabrowska            $this->docArray['header_count'][2] +
1607db0beafSAnna Dabrowska            $this->docArray['header_count'][3] +
1617db0beafSAnna Dabrowska            $this->docArray['header_count'][4] +
1627db0beafSAnna Dabrowska            $this->docArray['header_count'][5];
16313abf7ccSAndreas Gohr        $hc--; //we expect at least 1
16413abf7ccSAndreas Gohr        if ($hc > 0) {
1657db0beafSAnna Dabrowska            $hr = $this->docArray['chars'] / $hc;
166d723b313SAndreas Gohr
167d723b313SAndreas Gohr            // 1 point for too many headers
168d723b313SAndreas Gohr            if ($hr < 200) {
1697db0beafSAnna Dabrowska                $this->docArray['err']['manyheaders'] = 1;
170d723b313SAndreas Gohr            }
171d723b313SAndreas Gohr
172d723b313SAndreas Gohr            // 1 point for too few headers
1731b085f03SAndreas Gohr            if ($hr > 2000) {
1747db0beafSAnna Dabrowska                $this->docArray['err']['fewheaders'] = 1;
175d723b313SAndreas Gohr            }
176d723b313SAndreas Gohr        }
177d723b313SAndreas Gohr
178d723b313SAndreas Gohr        // 1 point when no link at all
1797db0beafSAnna Dabrowska        if (!$this->docArray['internal_links']) {
1807db0beafSAnna Dabrowska            $this->docArray['err']['nolink'] = 1;
181d723b313SAndreas Gohr        }
182d723b313SAndreas Gohr
183d723b313SAndreas Gohr        // 0.5 for broken links when too many
1847db0beafSAnna Dabrowska        if ($this->docArray['broken_links'] > 2) {
1857db0beafSAnna Dabrowska            $this->docArray['err']['brokenlink'] = $this->docArray['broken_links'] * 0.5;
186d723b313SAndreas Gohr        }
187d723b313SAndreas Gohr
188d723b313SAndreas Gohr        // 2 points for lot's of formatting
1897db0beafSAnna Dabrowska        if ($this->docArray['formatted'] && $this->docArray['chars'] / $this->docArray['formatted'] < 3) {
1907db0beafSAnna Dabrowska            $this->docArray['err']['manyformat'] = 2;
191d723b313SAndreas Gohr        }
192d723b313SAndreas Gohr
1939068e431SAndreas Gohr        // add up all scores
1942fc45e0cSsplitbrain        foreach ($this->docArray['err'] as $val) $this->docArray['score'] += $val;
1959068e431SAndreas Gohr
1964ea373cdSAndreas Gohr
1978fce80b1SAndreas Gohr        //we're done here
1987db0beafSAnna Dabrowska        $this->doc = serialize($this->docArray);
1998fce80b1SAndreas Gohr    }
2008fce80b1SAndreas Gohr
2018ae469bfSAndreas Gohr    /** @inheritdoc */
20276bbc49cSAnna Dabrowska    public function getFormat()
20376bbc49cSAnna Dabrowska    {
2048fce80b1SAndreas Gohr        return 'qc';
2058fce80b1SAndreas Gohr    }
2068fce80b1SAndreas Gohr
2078ae469bfSAndreas Gohr    /** @inheritdoc */
20876bbc49cSAnna Dabrowska    public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content')
20976bbc49cSAnna Dabrowska    {
210d723b313SAndreas Gohr        global $ID;
2118ae469bfSAndreas Gohr
2122fc45e0cSsplitbrain        $resolver = new PageResolver($ID);
2138ae469bfSAndreas Gohr        $id = $resolver->resolveId($id);
2148ae469bfSAndreas Gohr        $exists = page_exists($id);
215d723b313SAndreas Gohr
2168d7cf088SAndreas Gohr        // calculate link width
217d78155a4SAndreas Gohr        $a = explode(':', getNS($ID));
218d78155a4SAndreas Gohr        $b = explode(':', getNS($id));
2192fc45e0cSsplitbrain        while (isset($a[0]) && $a[0] === $b[0]) {
2208d7cf088SAndreas Gohr            array_shift($a);
2218d7cf088SAndreas Gohr            array_shift($b);
2228d7cf088SAndreas Gohr        }
223d78155a4SAndreas Gohr        $length = count($a) + count($b);
2247db0beafSAnna Dabrowska        $this->docArray['link_lengths'][] = $length;
2258d7cf088SAndreas Gohr
2267db0beafSAnna Dabrowska        $this->docArray['internal_links']++;
2277db0beafSAnna Dabrowska        if (!$exists) $this->docArray['broken_links']++;
2280476d180SAndreas Gohr    }
2290476d180SAndreas Gohr
2308ae469bfSAndreas Gohr    /** @inheritdoc */
23176bbc49cSAnna Dabrowska    public function externallink($url, $name = null)
23276bbc49cSAnna Dabrowska    {
2337db0beafSAnna Dabrowska        $this->docArray['external_links']++;
234d723b313SAndreas Gohr    }
2358fce80b1SAndreas Gohr
2368ae469bfSAndreas Gohr    /** @inheritdoc */
23776bbc49cSAnna Dabrowska    public function header($text, $level, $pos)
23876bbc49cSAnna Dabrowska    {
2397db0beafSAnna Dabrowska        $this->docArray['header_count'][$level]++;
2407db0beafSAnna Dabrowska        $this->docArray['header_struct'][] = $level;
2418fce80b1SAndreas Gohr    }
2428fce80b1SAndreas Gohr
2438ae469bfSAndreas Gohr    /** @inheritdoc */
24476bbc49cSAnna Dabrowska    public function smiley($smiley)
24576bbc49cSAnna Dabrowska    {
2467db0beafSAnna Dabrowska        if ($smiley == 'FIXME') $this->docArray['fixme']++;
2478fce80b1SAndreas Gohr    }
2488fce80b1SAndreas Gohr
2498ae469bfSAndreas Gohr    /** @inheritdoc */
25076bbc49cSAnna Dabrowska    public function linebreak()
25176bbc49cSAnna Dabrowska    {
2526afc1841Sthesunrise1983        if (!$this->tableopen) {
2537db0beafSAnna Dabrowska            $this->docArray['linebreak']++;
2548fce80b1SAndreas Gohr        }
2556afc1841Sthesunrise1983    }
2566afc1841Sthesunrise1983
2578ae469bfSAndreas Gohr    /** @inheritdoc */
258c8d45e58SAndreas Gohr    public function table_open($maxcols = null, $numrows = null, $pos = null)
25976bbc49cSAnna Dabrowska    {
2606afc1841Sthesunrise1983        $this->tableopen = true;
2616afc1841Sthesunrise1983    }
2626afc1841Sthesunrise1983
2638ae469bfSAndreas Gohr    /** @inheritdoc */
264c8d45e58SAndreas Gohr    public function table_close($pos = null)
26576bbc49cSAnna Dabrowska    {
2666afc1841Sthesunrise1983        $this->tableopen = false;
2676afc1841Sthesunrise1983    }
2688fce80b1SAndreas Gohr
2698ae469bfSAndreas Gohr    /** @inheritdoc */
27076bbc49cSAnna Dabrowska    public function hr()
27176bbc49cSAnna Dabrowska    {
2727db0beafSAnna Dabrowska        $this->docArray['hr']++;
2738fce80b1SAndreas Gohr    }
2748fce80b1SAndreas Gohr
2758ae469bfSAndreas Gohr    /** @inheritdoc */
276c8d45e58SAndreas Gohr    public function quote_open()
27776bbc49cSAnna Dabrowska    {
2787db0beafSAnna Dabrowska        $this->docArray['quote_count']++;
2798fce80b1SAndreas Gohr        $this->quotelevel++;
2807db0beafSAnna Dabrowska        $this->docArray['quote_nest'] = max($this->quotelevel, $this->docArray['quote_nest']);
2818fce80b1SAndreas Gohr    }
2828fce80b1SAndreas Gohr
2838ae469bfSAndreas Gohr    /** @inheritdoc */
284c8d45e58SAndreas Gohr    public function quote_close()
28576bbc49cSAnna Dabrowska    {
2868fce80b1SAndreas Gohr        $this->quotelevel--;
2878fce80b1SAndreas Gohr    }
2888fce80b1SAndreas Gohr
2898ae469bfSAndreas Gohr    /** @inheritdoc */
290c8d45e58SAndreas Gohr    public function strong_open()
29176bbc49cSAnna Dabrowska    {
292d723b313SAndreas Gohr        $this->formatting++;
293d723b313SAndreas Gohr    }
2948fce80b1SAndreas Gohr
2958ae469bfSAndreas Gohr    /** @inheritdoc */
296c8d45e58SAndreas Gohr    public function strong_close()
29776bbc49cSAnna Dabrowska    {
298d723b313SAndreas Gohr        $this->formatting--;
299d723b313SAndreas Gohr    }
300d723b313SAndreas Gohr
3018ae469bfSAndreas Gohr    /** @inheritdoc */
302c8d45e58SAndreas Gohr    public function emphasis_open()
30376bbc49cSAnna Dabrowska    {
304d723b313SAndreas Gohr        $this->formatting++;
305d723b313SAndreas Gohr    }
306d723b313SAndreas Gohr
3078ae469bfSAndreas Gohr    /** @inheritdoc */
308c8d45e58SAndreas Gohr    public function emphasis_close()
30976bbc49cSAnna Dabrowska    {
310d723b313SAndreas Gohr        $this->formatting--;
311d723b313SAndreas Gohr    }
312d723b313SAndreas Gohr
3138ae469bfSAndreas Gohr    /** @inheritdoc */
314c8d45e58SAndreas Gohr    public function underline_open()
31576bbc49cSAnna Dabrowska    {
316d723b313SAndreas Gohr        $this->formatting++;
317d723b313SAndreas Gohr    }
318d723b313SAndreas Gohr
3198ae469bfSAndreas Gohr    /** @inheritdoc */
320c8d45e58SAndreas Gohr    public function underline_close()
32176bbc49cSAnna Dabrowska    {
322d723b313SAndreas Gohr        $this->formatting--;
323d723b313SAndreas Gohr    }
324d723b313SAndreas Gohr
3258ae469bfSAndreas Gohr    /** @inheritdoc */
32676bbc49cSAnna Dabrowska    public function cdata($text)
32776bbc49cSAnna Dabrowska    {
328d723b313SAndreas Gohr        if (!$this->formatting) return;
329d723b313SAndreas Gohr
3308ae469bfSAndreas Gohr        $len = PhpString::strlen($text);
331d723b313SAndreas Gohr
332d723b313SAndreas Gohr        // 1 point for formattings longer than 500 chars
3337db0beafSAnna Dabrowska        if ($len > 500) $this->docArray['err']['longformat']++;
334d723b313SAndreas Gohr
3354bda998cSAndreas Gohr        // 1 point for each multiformatting
3367db0beafSAnna Dabrowska        if ($this->formatting > 1) $this->docArray['err']['multiformat'] += 1 * ($this->formatting - 1);
3374bda998cSAndreas Gohr
3387db0beafSAnna Dabrowska        $this->docArray['formatted'] += $len;
339d723b313SAndreas Gohr    }
3408fce80b1SAndreas Gohr}
341