xref: /plugin/combo/renderer/analytics.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1007225e5Sgerardnico<?php
2007225e5Sgerardnico
3007225e5Sgerardnico
4*04fd306cSNickeauuse ComboStrap\Meta\Field\BacklinkCount;
5c3437056SNickeauuse ComboStrap\Canonical;
6*04fd306cSNickeauuse ComboStrap\ExceptionCompile;
7*04fd306cSNickeauuse ComboStrap\ExceptionNotExists;
8*04fd306cSNickeauuse ComboStrap\ExceptionNotFound;
9*04fd306cSNickeauuse ComboStrap\ExceptionRuntimeInternal;
10*04fd306cSNickeauuse ComboStrap\FetcherMarkup;
11*04fd306cSNickeauuse ComboStrap\LogUtility;
12*04fd306cSNickeauuse ComboStrap\MarkupPath;
13*04fd306cSNickeauuse ComboStrap\Meta\Field\PageH1;
14*04fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDbStore;
15*04fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDokuWikiStore;
16*04fd306cSNickeauuse ComboStrap\Mime;
17c3437056SNickeauuse ComboStrap\PageTitle;
1837748cd8SNickeauuse ComboStrap\StringUtility;
19*04fd306cSNickeauuse ComboStrap\WikiPath;
20007225e5Sgerardnicouse dokuwiki\ChangeLog\PageChangeLog;
21007225e5Sgerardnico
2237748cd8SNickeau
2337748cd8SNickeaurequire_once(__DIR__ . '/../ComboStrap/PluginUtility.php');
24007225e5Sgerardnico
25007225e5Sgerardnico
26007225e5Sgerardnico/**
27007225e5Sgerardnico * A analysis Renderer that exports stats/quality/metadata in a json format
28007225e5Sgerardnico * You can export the data with
29007225e5Sgerardnico * doku.php?id=somepage&do=export_combo_analytics
30*04fd306cSNickeau *
31*04fd306cSNickeau * TODO: Move the metadata part to the metadata render and the quality part to the indexer.
32007225e5Sgerardnico */
33007225e5Sgerardnicoclass renderer_plugin_combo_analytics extends Doku_Renderer
34007225e5Sgerardnico{
357c33ecc6Sgerardnico
36007225e5Sgerardnico    const PLAINTEXT = 'formatted';
37007225e5Sgerardnico    const RESULT = "result";
38007225e5Sgerardnico    const DESCRIPTION = "description";
39007225e5Sgerardnico    const PASSED = "Passed";
40007225e5Sgerardnico    const FAILED = "Failed";
41007225e5Sgerardnico    const FIXME = 'fixme';
42007225e5Sgerardnico
43007225e5Sgerardnico    /**
44007225e5Sgerardnico     * Rules key
45007225e5Sgerardnico     */
46007225e5Sgerardnico    const RULE_WORDS_MINIMAL = 'words_min';
47007225e5Sgerardnico    const RULE_OUTLINE_STRUCTURE = "outline_structure";
48007225e5Sgerardnico    const RULE_INTERNAL_BACKLINKS_MIN = 'internal_backlinks_min';
49007225e5Sgerardnico    const RULE_WORDS_MAXIMAL = "words_max";
50007225e5Sgerardnico    const RULE_AVERAGE_WORDS_BY_SECTION_MIN = 'words_by_section_avg_min';
51007225e5Sgerardnico    const RULE_AVERAGE_WORDS_BY_SECTION_MAX = 'words_by_section_avg_max';
52007225e5Sgerardnico    const RULE_INTERNAL_LINKS_MIN = 'internal_links_min';
53007225e5Sgerardnico    const RULE_INTERNAL_BROKEN_LINKS_MAX = 'internal_links_broken_max';
54007225e5Sgerardnico    const RULE_DESCRIPTION_PRESENT = 'description_present';
55007225e5Sgerardnico    const RULE_FIXME = "fixme_min";
56007225e5Sgerardnico    const RULE_TITLE_PRESENT = "title_present";
57007225e5Sgerardnico    const RULE_CANONICAL_PRESENT = "canonical_present";
58aa3cb38fSgerardnico    const QUALITY_RULES = [
59aa3cb38fSgerardnico        self::RULE_CANONICAL_PRESENT,
60aa3cb38fSgerardnico        self::RULE_DESCRIPTION_PRESENT,
61aa3cb38fSgerardnico        self::RULE_FIXME,
62aa3cb38fSgerardnico        self::RULE_INTERNAL_BACKLINKS_MIN,
63aa3cb38fSgerardnico        self::RULE_INTERNAL_BROKEN_LINKS_MAX,
64aa3cb38fSgerardnico        self::RULE_INTERNAL_LINKS_MIN,
65aa3cb38fSgerardnico        self::RULE_OUTLINE_STRUCTURE,
66aa3cb38fSgerardnico        self::RULE_TITLE_PRESENT,
67aa3cb38fSgerardnico        self::RULE_WORDS_MINIMAL,
68aa3cb38fSgerardnico        self::RULE_WORDS_MAXIMAL,
69aa3cb38fSgerardnico        self::RULE_AVERAGE_WORDS_BY_SECTION_MIN,
70aa3cb38fSgerardnico        self::RULE_AVERAGE_WORDS_BY_SECTION_MAX
71aa3cb38fSgerardnico    ];
72007225e5Sgerardnico
73007225e5Sgerardnico    /**
74007225e5Sgerardnico     * The default man
75007225e5Sgerardnico     */
76007225e5Sgerardnico    const CONF_MANDATORY_QUALITY_RULES_DEFAULT_VALUE = [
77007225e5Sgerardnico        self::RULE_WORDS_MINIMAL,
78007225e5Sgerardnico        self::RULE_INTERNAL_BACKLINKS_MIN,
79007225e5Sgerardnico        self::RULE_INTERNAL_LINKS_MIN
80007225e5Sgerardnico    ];
81007225e5Sgerardnico    const CONF_MANDATORY_QUALITY_RULES = "mandatoryQualityRules";
82007225e5Sgerardnico
83007225e5Sgerardnico    /**
84007225e5Sgerardnico     * Quality Score factors
85007225e5Sgerardnico     * They are used to calculate the score
86007225e5Sgerardnico     */
87007225e5Sgerardnico    const CONF_QUALITY_SCORE_INTERNAL_BACKLINK_FACTOR = 'qualityScoreInternalBacklinksFactor';
88007225e5Sgerardnico    const CONF_QUALITY_SCORE_INTERNAL_LINK_FACTOR = 'qualityScoreInternalLinksFactor';
89007225e5Sgerardnico    const CONF_QUALITY_SCORE_TITLE_PRESENT = 'qualityScoreTitlePresent';
90007225e5Sgerardnico    const CONF_QUALITY_SCORE_CORRECT_HEADER_STRUCTURE = 'qualityScoreCorrectOutline';
91007225e5Sgerardnico    const CONF_QUALITY_SCORE_CORRECT_CONTENT = 'qualityScoreCorrectContentLength';
92007225e5Sgerardnico    const CONF_QUALITY_SCORE_NO_FIXME = 'qualityScoreNoFixMe';
93007225e5Sgerardnico    const CONF_QUALITY_SCORE_CORRECT_WORD_SECTION_AVERAGE = 'qualityScoreCorrectWordSectionAvg';
94007225e5Sgerardnico    const CONF_QUALITY_SCORE_INTERNAL_LINK_BROKEN_FACTOR = 'qualityScoreNoBrokenLinks';
95007225e5Sgerardnico    const CONF_QUALITY_SCORE_CHANGES_FACTOR = 'qualityScoreChangesFactor';
96007225e5Sgerardnico    const CONF_QUALITY_SCORE_DESCRIPTION_PRESENT = 'qualityScoreDescriptionPresent';
97007225e5Sgerardnico    const CONF_QUALITY_SCORE_CANONICAL_PRESENT = 'qualityScoreCanonicalPresent';
9808ca4f85Sgerardnico    const SCORING = "scoring";
9908ca4f85Sgerardnico    const SCORE = "score";
100ebdc69ceSgerardnico    const HEADER_STRUCT = 'header_struct';
101531e725cSNickeau    const RENDERER_NAME_MODE = "combo_" . renderer_plugin_combo_analytics::RENDERER_FORMAT;
102c3437056SNickeau
103*04fd306cSNickeau
104531e725cSNickeau    /**
105531e725cSNickeau     * The format returned by the renderer
106531e725cSNickeau     */
107531e725cSNickeau    const RENDERER_FORMAT = "analytics";
108*04fd306cSNickeau    public const QUALITY = 'quality';
109*04fd306cSNickeau    public const DETAILS = 'details';
110*04fd306cSNickeau    /**
111*04fd306cSNickeau     * An array of info for errors mostly
112*04fd306cSNickeau     */
113*04fd306cSNickeau    public const INFO = "info";
114*04fd306cSNickeau    public const INTERNAL_LINK_COUNT = 'internal_link_count';
115*04fd306cSNickeau    public const CHAR_COUNT = 'char_count';
116*04fd306cSNickeau    public const FAILED_MANDATORY_RULES = 'failed_mandatory_rules';
117*04fd306cSNickeau    public const EDITS_COUNT = 'edits_count';
118*04fd306cSNickeau    public const LOCAL_LINK_COUNT = "local_link_count";
119*04fd306cSNickeau    public const WINDOWS_SHARE_COUNT = "windows_share_count";
120*04fd306cSNickeau    public const SYNTAX_COUNT = "syntax_count";
121*04fd306cSNickeau    /**
122*04fd306cSNickeau     * Constant in Key or value
123*04fd306cSNickeau     */
124*04fd306cSNickeau    public const HEADER_POSITION = 'header_id';
125*04fd306cSNickeau    public const INTERNAL_BROKEN_MEDIA_COUNT = 'internal_broken_media_count';
126*04fd306cSNickeau    public const TEMPLATE_LINK_COUNT = 'template_link_count';
127*04fd306cSNickeau    public const STATISTICS = "statistics";
128*04fd306cSNickeau    public const INTERWIKI_LINK_COUNT = "interwiki_link_count";
129*04fd306cSNickeau    public const HEADING_COUNT = 'heading_count';
130*04fd306cSNickeau    public const MEDIA_COUNT = 'media_count';
131*04fd306cSNickeau    public const EXTERNAL_MEDIA_COUNT = 'external_media_count';
132*04fd306cSNickeau    public const INTERNAL_LINK_DISTANCE = 'internal_link_distance';
133*04fd306cSNickeau    public const INTERNAL_LINK_BROKEN_COUNT = 'internal_broken_link_count';
134*04fd306cSNickeau    public const EMAIL_COUNT = "email_count";
135*04fd306cSNickeau    public const EXTERNAL_LINK_COUNT = 'external_link_count';
136*04fd306cSNickeau    public const LOW = "low";
137*04fd306cSNickeau    public const WORD_COUNT = 'word_count';
138*04fd306cSNickeau    public const RULES = "rules";
139*04fd306cSNickeau    public const METADATA = 'metadata';
140*04fd306cSNickeau    public const INTERNAL_MEDIA_COUNT = 'internal_media_count';
141007225e5Sgerardnico
142aa3cb38fSgerardnico
143007225e5Sgerardnico    /**
144007225e5Sgerardnico     * The processing data
145007225e5Sgerardnico     * that should be {@link  renderer_plugin_combo_analysis::reset()}
146007225e5Sgerardnico     */
147007225e5Sgerardnico    public $stats = array(); // the stats
14837748cd8SNickeau    protected $metadata = array(); // the metadata in frontmatter
149007225e5Sgerardnico    protected $headerId = 0; // the id of the header on the page (first, second, ...)
150007225e5Sgerardnico
151007225e5Sgerardnico    /**
152007225e5Sgerardnico     * Don't known this variable ?
153007225e5Sgerardnico     */
154007225e5Sgerardnico    protected $quotelevel = 0;
155007225e5Sgerardnico    protected $formattingBracket = 0;
156007225e5Sgerardnico    protected $tableopen = false;
157007225e5Sgerardnico    private $plainTextId = 0;
1582c067407Sgerardnico    /**
159*04fd306cSNickeau     * @var MarkupPath
1602c067407Sgerardnico     */
161*04fd306cSNickeau    private MarkupPath $page;
162*04fd306cSNickeau
163*04fd306cSNickeau    /**
164*04fd306cSNickeau     * @throws ExceptionNotExists - if the file does not exists
165*04fd306cSNickeau     */
166*04fd306cSNickeau    public static function createAnalyticsFetcherForPageFragment(MarkupPath $markupPath): FetcherMarkup
167*04fd306cSNickeau    {
168*04fd306cSNickeau        $path = $markupPath->getPathObject();
169*04fd306cSNickeau        if (!($path instanceof WikiPath)) {
170*04fd306cSNickeau            throw new ExceptionRuntimeInternal("The path ($path) is not a wiki path");
171*04fd306cSNickeau        }
172*04fd306cSNickeau        return FetcherMarkup::confRoot()
173*04fd306cSNickeau            ->setRequestedExecutingPath($path)
174*04fd306cSNickeau            ->setRequestedContextPath($path)
175*04fd306cSNickeau            ->setRequestedMime(Mime::getJson())
176*04fd306cSNickeau            ->setRequestedRenderer(self::RENDERER_NAME_MODE)
177*04fd306cSNickeau            ->build();
178*04fd306cSNickeau
179*04fd306cSNickeau    }
180*04fd306cSNickeau
181*04fd306cSNickeau    public static function getMime(): Mime
182*04fd306cSNickeau    {
183*04fd306cSNickeau        return Mime::create(self::RENDERER_NAME_MODE . "/json");
184*04fd306cSNickeau    }
1852c067407Sgerardnico
186e8b2ff59SNickeau    /**
187e8b2ff59SNickeau     * Get and unset a value from an array
188e8b2ff59SNickeau     * @param array $array
189e8b2ff59SNickeau     * @param $key
190e8b2ff59SNickeau     * @param $default
191e8b2ff59SNickeau     * @return mixed
192e8b2ff59SNickeau     */
193e8b2ff59SNickeau    private static function getAndUnset(array &$array, $key, $default)
194e8b2ff59SNickeau    {
195e8b2ff59SNickeau        if (isset($array[$key])) {
196e8b2ff59SNickeau            $value = $array[$key];
197e8b2ff59SNickeau            unset($array[$key]);
198e8b2ff59SNickeau            return $value;
199e8b2ff59SNickeau        }
200e8b2ff59SNickeau        return $default;
201e8b2ff59SNickeau
202e8b2ff59SNickeau    }
203e8b2ff59SNickeau
2042c067407Sgerardnico    public function document_start()
2052c067407Sgerardnico    {
2067c33ecc6Sgerardnico        $this->reset();
207*04fd306cSNickeau        try {
208*04fd306cSNickeau            $this->page = MarkupPath::createPageFromExecutingId();
209*04fd306cSNickeau        } catch (ExceptionCompile $e) {
210*04fd306cSNickeau            LogUtility::msg("The global ID is unknown, we were unable to instantiate the requested page in analytics");
211*04fd306cSNickeau        }
2122c067407Sgerardnico
2132c067407Sgerardnico    }
214007225e5Sgerardnico
215007225e5Sgerardnico
216007225e5Sgerardnico    /**
217007225e5Sgerardnico     * Here the score is calculated
218007225e5Sgerardnico     */
219007225e5Sgerardnico    public function document_end() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
220007225e5Sgerardnico    {
221007225e5Sgerardnico        /**
222f3748b38Sgerardnico         * The exported object
223f3748b38Sgerardnico         */
224f3748b38Sgerardnico        $statExport = $this->stats;
225f3748b38Sgerardnico
226f3748b38Sgerardnico        /**
227007225e5Sgerardnico         * The metadata
228007225e5Sgerardnico         */
229007225e5Sgerardnico        global $ID;
230fa5961eaSgerardnico        $dokuWikiMetadata = p_get_metadata($ID);
231007225e5Sgerardnico
232007225e5Sgerardnico        /**
233f3748b38Sgerardnico         * Edit author stats
234f3748b38Sgerardnico         */
235f3748b38Sgerardnico        $changelog = new PageChangeLog($ID);
236f3748b38Sgerardnico        $revs = $changelog->getRevisions(0, 10000);
237fa5961eaSgerardnico        array_push($revs, $dokuWikiMetadata['last_change']['date']);
238*04fd306cSNickeau        $statExport[self::EDITS_COUNT] = count($revs);
239f3748b38Sgerardnico        foreach ($revs as $rev) {
2402128d419Sgerardnico
241ebdc69ceSgerardnico
242ebdc69ceSgerardnico            /**
243ebdc69ceSgerardnico             * Init the authors array
244ebdc69ceSgerardnico             */
245ebdc69ceSgerardnico            if (!array_key_exists('authors', $statExport)) {
246ebdc69ceSgerardnico                $statExport['authors'] = [];
247f3748b38Sgerardnico            }
248ebdc69ceSgerardnico            /**
249ebdc69ceSgerardnico             * Analytics by users
250ebdc69ceSgerardnico             */
2512128d419Sgerardnico            $info = $changelog->getRevisionInfo($rev);
2522128d419Sgerardnico            if (is_array($info)) {
253ebdc69ceSgerardnico                $user = "*";
254ebdc69ceSgerardnico                if (array_key_exists('user', $info)) {
255ebdc69ceSgerardnico                    $user = $info['user'];
256ebdc69ceSgerardnico                }
257ebdc69ceSgerardnico                if (!array_key_exists('authors', $statExport['authors'])) {
258ebdc69ceSgerardnico                    $statExport['authors'][$user] = 0;
259ebdc69ceSgerardnico                }
260ebdc69ceSgerardnico                $statExport['authors'][$user] += 1;
261f3748b38Sgerardnico            }
2622128d419Sgerardnico        }
263f3748b38Sgerardnico
264f3748b38Sgerardnico        /**
265007225e5Sgerardnico         * Word and chars count
266007225e5Sgerardnico         * The word count does not take into account
267007225e5Sgerardnico         * words with non-words characters such as < =
268007225e5Sgerardnico         * Therefore the node and attribute are not taken in the count
269007225e5Sgerardnico         */
270007225e5Sgerardnico        $text = rawWiki($ID);
271*04fd306cSNickeau        $statExport[self::CHAR_COUNT] = strlen($text);
272*04fd306cSNickeau        $statExport[self::WORD_COUNT] = StringUtility::getWordCount($text);
273007225e5Sgerardnico
274007225e5Sgerardnico
275007225e5Sgerardnico        /**
276007225e5Sgerardnico         * Internal link distance summary calculation
277007225e5Sgerardnico         */
278*04fd306cSNickeau        if (array_key_exists(self::INTERNAL_LINK_DISTANCE, $statExport)) {
279*04fd306cSNickeau            $linkLengths = $statExport[self::INTERNAL_LINK_DISTANCE];
280*04fd306cSNickeau            unset($statExport[self::INTERNAL_LINK_DISTANCE]);
281007225e5Sgerardnico            $countBacklinks = count($linkLengths);
282*04fd306cSNickeau            $statExport[self::INTERNAL_LINK_DISTANCE]['avg'] = null;
283*04fd306cSNickeau            $statExport[self::INTERNAL_LINK_DISTANCE]['max'] = null;
284*04fd306cSNickeau            $statExport[self::INTERNAL_LINK_DISTANCE]['min'] = null;
285007225e5Sgerardnico            if ($countBacklinks > 0) {
286*04fd306cSNickeau                $statExport[self::INTERNAL_LINK_DISTANCE]['avg'] = array_sum($linkLengths) / $countBacklinks;
287*04fd306cSNickeau                $statExport[self::INTERNAL_LINK_DISTANCE]['max'] = max($linkLengths);
288*04fd306cSNickeau                $statExport[self::INTERNAL_LINK_DISTANCE]['min'] = min($linkLengths);
289007225e5Sgerardnico            }
290007225e5Sgerardnico        }
291007225e5Sgerardnico
292007225e5Sgerardnico        /**
293007225e5Sgerardnico         * Quality Report / Rules
294007225e5Sgerardnico         */
295007225e5Sgerardnico        // The array that hold the results of the quality rules
296007225e5Sgerardnico        $ruleResults = array();
297007225e5Sgerardnico        // The array that hold the quality score details
298007225e5Sgerardnico        $qualityScores = array();
299007225e5Sgerardnico
300007225e5Sgerardnico
301007225e5Sgerardnico        /**
302007225e5Sgerardnico         * No fixme
303007225e5Sgerardnico         */
304ebdc69ceSgerardnico        if (array_key_exists(self::FIXME, $this->stats)) {
305007225e5Sgerardnico            $fixmeCount = $this->stats[self::FIXME];
306007225e5Sgerardnico            $statExport[self::FIXME] = $fixmeCount == null ? 0 : $fixmeCount;
307007225e5Sgerardnico            if ($fixmeCount != 0) {
308007225e5Sgerardnico                $ruleResults[self::RULE_FIXME] = self::FAILED;
309007225e5Sgerardnico                $qualityScores['no_' . self::FIXME] = 0;
310007225e5Sgerardnico            } else {
311007225e5Sgerardnico                $ruleResults[self::RULE_FIXME] = self::PASSED;
3127c33ecc6Sgerardnico                $qualityScores['no_' . self::FIXME] = $this->getConf(self::CONF_QUALITY_SCORE_NO_FIXME, 1);
313007225e5Sgerardnico            }
314ebdc69ceSgerardnico        }
315007225e5Sgerardnico
316007225e5Sgerardnico        /**
317007225e5Sgerardnico         * A title should be present
318007225e5Sgerardnico         */
31908ca4f85Sgerardnico        $titleScore = $this->getConf(self::CONF_QUALITY_SCORE_TITLE_PRESENT, 10);
320c3437056SNickeau        if (empty($this->metadata[PageTitle::TITLE])) {
321007225e5Sgerardnico            $ruleResults[self::RULE_TITLE_PRESENT] = self::FAILED;
322c3437056SNickeau            $ruleInfo[self::RULE_TITLE_PRESENT] = "Add a title for {$titleScore} points";
323c3437056SNickeau            $this->metadata[PageTitle::TITLE] = $dokuWikiMetadata[PageTitle::TITLE];
324007225e5Sgerardnico            $qualityScores[self::RULE_TITLE_PRESENT] = 0;
325007225e5Sgerardnico        } else {
3267c33ecc6Sgerardnico            $qualityScores[self::RULE_TITLE_PRESENT] = $titleScore;
327007225e5Sgerardnico            $ruleResults[self::RULE_TITLE_PRESENT] = self::PASSED;
328007225e5Sgerardnico        }
329007225e5Sgerardnico
330007225e5Sgerardnico        /**
331007225e5Sgerardnico         * A description should be present
332007225e5Sgerardnico         */
33308ca4f85Sgerardnico        $descScore = $this->getConf(self::CONF_QUALITY_SCORE_DESCRIPTION_PRESENT, 8);
33437748cd8SNickeau        if (empty($this->metadata[self::DESCRIPTION])) {
335007225e5Sgerardnico            $ruleResults[self::RULE_DESCRIPTION_PRESENT] = self::FAILED;
336c3437056SNickeau            $ruleInfo[self::RULE_DESCRIPTION_PRESENT] = "Add a description for {$descScore} points";
33737748cd8SNickeau            $this->metadata[self::DESCRIPTION] = $dokuWikiMetadata[self::DESCRIPTION]["abstract"];
338007225e5Sgerardnico            $qualityScores[self::RULE_DESCRIPTION_PRESENT] = 0;
339007225e5Sgerardnico        } else {
3407c33ecc6Sgerardnico            $qualityScores[self::RULE_DESCRIPTION_PRESENT] = $descScore;
341007225e5Sgerardnico            $ruleResults[self::RULE_DESCRIPTION_PRESENT] = self::PASSED;
342007225e5Sgerardnico        }
343007225e5Sgerardnico
344007225e5Sgerardnico        /**
345007225e5Sgerardnico         * A canonical should be present
346007225e5Sgerardnico         */
34708ca4f85Sgerardnico        $canonicalScore = $this->getConf(self::CONF_QUALITY_SCORE_CANONICAL_PRESENT, 5);
348c3437056SNickeau        if (empty($this->metadata[Canonical::PROPERTY_NAME])) {
349f3748b38Sgerardnico            global $conf;
350f3748b38Sgerardnico            $root = $conf['start'];
3514cadd4f8SNickeau            if ($ID !== $root) {
352007225e5Sgerardnico                $qualityScores[self::RULE_CANONICAL_PRESENT] = 0;
353007225e5Sgerardnico                $ruleResults[self::RULE_CANONICAL_PRESENT] = self::FAILED;
354c3437056SNickeau                // no link to the documentation because we don't want any html in the json
355c3437056SNickeau                $ruleInfo[self::RULE_CANONICAL_PRESENT] = "Add a canonical for {$canonicalScore} points";
356f3748b38Sgerardnico            }
357007225e5Sgerardnico        } else {
3587c33ecc6Sgerardnico            $qualityScores[self::RULE_CANONICAL_PRESENT] = $canonicalScore;
359007225e5Sgerardnico            $ruleResults[self::RULE_CANONICAL_PRESENT] = self::PASSED;
360007225e5Sgerardnico        }
361007225e5Sgerardnico
362007225e5Sgerardnico        /**
363007225e5Sgerardnico         * Outline / Header structure
364007225e5Sgerardnico         */
365007225e5Sgerardnico        $treeError = 0;
366007225e5Sgerardnico        $headersCount = 0;
367*04fd306cSNickeau        if (array_key_exists(self::HEADER_POSITION, $this->stats)) {
368*04fd306cSNickeau            $headersCount = count($this->stats[self::HEADER_POSITION]);
369*04fd306cSNickeau            unset($statExport[self::HEADER_POSITION]);
370007225e5Sgerardnico            for ($i = 1; $i < $headersCount; $i++) {
371ebdc69ceSgerardnico                $currentHeaderLevel = $this->stats[self::HEADER_STRUCT][$i];
372ebdc69ceSgerardnico                $previousHeaderLevel = $this->stats[self::HEADER_STRUCT][$i - 1];
373007225e5Sgerardnico                if ($currentHeaderLevel - $previousHeaderLevel > 1) {
374007225e5Sgerardnico                    $treeError += 1;
375007225e5Sgerardnico                    $ruleInfo[self::RULE_OUTLINE_STRUCTURE] = "The " . $i . " header (h" . $currentHeaderLevel . ") has a level bigger than its precedent (" . $previousHeaderLevel . ")";
376007225e5Sgerardnico                }
377007225e5Sgerardnico            }
378ebdc69ceSgerardnico            unset($statExport[self::HEADER_STRUCT]);
379007225e5Sgerardnico        }
380eee76a3dSgerardnico        $outlinePoints = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_HEADER_STRUCTURE, 3);
381007225e5Sgerardnico        if ($treeError > 0 || $headersCount == 0) {
382007225e5Sgerardnico            $qualityScores['correct_outline'] = 0;
383007225e5Sgerardnico            $ruleResults[self::RULE_OUTLINE_STRUCTURE] = self::FAILED;
384007225e5Sgerardnico            if ($headersCount == 0) {
385eee76a3dSgerardnico                $ruleInfo[self::RULE_OUTLINE_STRUCTURE] = "Add headings to create a document outline for {$outlinePoints} points";
386007225e5Sgerardnico            }
387007225e5Sgerardnico        } else {
388eee76a3dSgerardnico            $qualityScores['correct_outline'] = $outlinePoints;
389007225e5Sgerardnico            $ruleResults[self::RULE_OUTLINE_STRUCTURE] = self::PASSED;
390007225e5Sgerardnico        }
391007225e5Sgerardnico
392007225e5Sgerardnico
393007225e5Sgerardnico        /**
394007225e5Sgerardnico         * Document length
395007225e5Sgerardnico         */
396007225e5Sgerardnico        $minimalWordCount = 50;
397007225e5Sgerardnico        $maximalWordCount = 1500;
398007225e5Sgerardnico        $correctContentLength = true;
39908ca4f85Sgerardnico        $correctLengthScore = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_CONTENT, 10);
400*04fd306cSNickeau        $missingWords = $minimalWordCount - $statExport[self::WORD_COUNT];
40108ca4f85Sgerardnico        if ($missingWords > 0) {
402007225e5Sgerardnico            $ruleResults[self::RULE_WORDS_MINIMAL] = self::FAILED;
403007225e5Sgerardnico            $correctContentLength = false;
40408ca4f85Sgerardnico            $ruleInfo[self::RULE_WORDS_MINIMAL] = "Add {$missingWords} words to get {$correctLengthScore} points";
405007225e5Sgerardnico        } else {
406007225e5Sgerardnico            $ruleResults[self::RULE_WORDS_MINIMAL] = self::PASSED;
407007225e5Sgerardnico        }
408*04fd306cSNickeau        $tooMuchWords = $statExport[self::WORD_COUNT] - $maximalWordCount;
40908ca4f85Sgerardnico        if ($tooMuchWords > 0) {
410007225e5Sgerardnico            $ruleResults[self::RULE_WORDS_MAXIMAL] = self::FAILED;
41108ca4f85Sgerardnico            $ruleInfo[self::RULE_WORDS_MAXIMAL] = "Delete {$tooMuchWords} words to get {$correctLengthScore} points";
412007225e5Sgerardnico            $correctContentLength = false;
413007225e5Sgerardnico        } else {
414007225e5Sgerardnico            $ruleResults[self::RULE_WORDS_MAXIMAL] = self::PASSED;
415007225e5Sgerardnico        }
416007225e5Sgerardnico        if ($correctContentLength) {
41708ca4f85Sgerardnico            $qualityScores['correct_content_length'] = $correctLengthScore;
418007225e5Sgerardnico        } else {
419007225e5Sgerardnico            $qualityScores['correct_content_length'] = 0;
420007225e5Sgerardnico        }
421007225e5Sgerardnico
422007225e5Sgerardnico
423007225e5Sgerardnico        /**
424007225e5Sgerardnico         * Average Number of words by header section to text ratio
425007225e5Sgerardnico         */
426*04fd306cSNickeau        $headers = $this->stats[self::HEADING_COUNT];
427007225e5Sgerardnico        if ($headers != null) {
428007225e5Sgerardnico            $headerCount = array_sum($headers);
429007225e5Sgerardnico            $headerCount--; // h1 is supposed to have no words
430007225e5Sgerardnico            if ($headerCount > 0) {
431007225e5Sgerardnico
432*04fd306cSNickeau                $avgWordsCountBySection = round($this->stats[self::WORD_COUNT] / $headerCount);
433007225e5Sgerardnico                $statExport['word_section_count']['avg'] = $avgWordsCountBySection;
434007225e5Sgerardnico
435007225e5Sgerardnico                /**
436007225e5Sgerardnico                 * Min words by header section
437007225e5Sgerardnico                 */
438007225e5Sgerardnico                $wordsByHeaderMin = 20;
439007225e5Sgerardnico                /**
440007225e5Sgerardnico                 * Max words by header section
441007225e5Sgerardnico                 */
442007225e5Sgerardnico                $wordsByHeaderMax = 300;
443007225e5Sgerardnico                $correctAverageWordsBySection = true;
444007225e5Sgerardnico                if ($avgWordsCountBySection < $wordsByHeaderMin) {
445007225e5Sgerardnico                    $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = self::FAILED;
446007225e5Sgerardnico                    $correctAverageWordsBySection = false;
44708ca4f85Sgerardnico                    $ruleInfo[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = "The number of words by section is less than {$wordsByHeaderMin}";
448007225e5Sgerardnico                } else {
449007225e5Sgerardnico                    $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = self::PASSED;
450007225e5Sgerardnico                }
451007225e5Sgerardnico                if ($avgWordsCountBySection > $wordsByHeaderMax) {
452007225e5Sgerardnico                    $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = self::FAILED;
453007225e5Sgerardnico                    $correctAverageWordsBySection = false;
454007225e5Sgerardnico                    $ruleInfo[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = "The number of words by section is more than {$wordsByHeaderMax}";
455007225e5Sgerardnico                } else {
456007225e5Sgerardnico                    $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = self::PASSED;
457007225e5Sgerardnico                }
458007225e5Sgerardnico                if ($correctAverageWordsBySection) {
459007225e5Sgerardnico                    $qualityScores['correct_word_avg_by_section'] = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_WORD_SECTION_AVERAGE, 10);
460007225e5Sgerardnico                } else {
461007225e5Sgerardnico                    $qualityScores['correct_word_avg_by_section'] = 0;
462007225e5Sgerardnico                }
463007225e5Sgerardnico
464007225e5Sgerardnico            }
465007225e5Sgerardnico        }
466007225e5Sgerardnico
467007225e5Sgerardnico        /**
468007225e5Sgerardnico         * Internal Backlinks rule
469007225e5Sgerardnico         *
470c3437056SNickeau         * We used the database table to get the backlinks
471c3437056SNickeau         * because the replication is based on it
472c3437056SNickeau         * If the dokuwiki index is not up to date, we may got
473c3437056SNickeau         * inconsistency
474007225e5Sgerardnico         */
475*04fd306cSNickeau        try {
476c3437056SNickeau            $countBacklinks = BacklinkCount::createFromResource($this->page)
477c3437056SNickeau                ->setReadStore(MetadataDbStore::class)
478c3437056SNickeau                ->getValueOrDefault();
479*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
480*04fd306cSNickeau            $countBacklinks = 0;
481*04fd306cSNickeau        }
482c3437056SNickeau        $statExport[BacklinkCount::getPersistentName()] = $countBacklinks;
483d262537cSgerardnico        $backlinkScore = $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_BACKLINK_FACTOR, 1);
484007225e5Sgerardnico        if ($countBacklinks == 0) {
485c3437056SNickeau
486c3437056SNickeau            $qualityScores[BacklinkCount::getPersistentName()] = 0;
487007225e5Sgerardnico            $ruleResults[self::RULE_INTERNAL_BACKLINKS_MIN] = self::FAILED;
488d262537cSgerardnico            $ruleInfo[self::RULE_INTERNAL_BACKLINKS_MIN] = "Add backlinks for {$backlinkScore} point each";
489c3437056SNickeau
490007225e5Sgerardnico        } else {
491d262537cSgerardnico
492c3437056SNickeau            $qualityScores[BacklinkCount::getPersistentName()] = $countBacklinks * $backlinkScore;
493007225e5Sgerardnico            $ruleResults[self::RULE_INTERNAL_BACKLINKS_MIN] = self::PASSED;
494007225e5Sgerardnico        }
495007225e5Sgerardnico
496007225e5Sgerardnico        /**
497007225e5Sgerardnico         * Internal links
498007225e5Sgerardnico         */
499*04fd306cSNickeau        $internalLinksCount = $this->stats[self::INTERNAL_LINK_COUNT];
500d262537cSgerardnico        $internalLinkScore = $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_LINK_FACTOR, 1);
501007225e5Sgerardnico        if ($internalLinksCount == 0) {
502*04fd306cSNickeau            $qualityScores[self::INTERNAL_LINK_COUNT] = 0;
503007225e5Sgerardnico            $ruleResults[self::RULE_INTERNAL_LINKS_MIN] = self::FAILED;
504d262537cSgerardnico            $ruleInfo[self::RULE_INTERNAL_LINKS_MIN] = "Add internal links for {$internalLinkScore} point each";
505007225e5Sgerardnico        } else {
506007225e5Sgerardnico            $ruleResults[self::RULE_INTERNAL_LINKS_MIN] = self::PASSED;
507*04fd306cSNickeau            $qualityScores[self::INTERNAL_LINK_COUNT] = $countBacklinks * $internalLinkScore;
508007225e5Sgerardnico        }
509007225e5Sgerardnico
510007225e5Sgerardnico        /**
511007225e5Sgerardnico         * Broken Links
512007225e5Sgerardnico         */
513d262537cSgerardnico        $brokenLinkScore = $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_LINK_BROKEN_FACTOR, 2);
514ebdc69ceSgerardnico        $brokenLinksCount = 0;
515*04fd306cSNickeau        if (array_key_exists(self::INTERNAL_LINK_BROKEN_COUNT, $this->stats)) {
516*04fd306cSNickeau            $brokenLinksCount = $this->stats[self::INTERNAL_LINK_BROKEN_COUNT];
517ebdc69ceSgerardnico        }
518007225e5Sgerardnico        if ($brokenLinksCount > 2) {
519*04fd306cSNickeau            $qualityScores['no_' . self::INTERNAL_LINK_BROKEN_COUNT] = 0;
520007225e5Sgerardnico            $ruleResults[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = self::FAILED;
521d262537cSgerardnico            $ruleInfo[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = "Delete the {$brokenLinksCount} broken links and add {$brokenLinkScore} points";
522007225e5Sgerardnico        } else {
523*04fd306cSNickeau            $qualityScores['no_' . self::INTERNAL_LINK_BROKEN_COUNT] = $brokenLinkScore;
524007225e5Sgerardnico            $ruleResults[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = self::PASSED;
525007225e5Sgerardnico        }
526007225e5Sgerardnico
527007225e5Sgerardnico        /**
528e8b2ff59SNickeau         * Media
529e8b2ff59SNickeau         */
530e8b2ff59SNickeau        $mediasStats = [
531*04fd306cSNickeau            "total_count" => self::getAndUnset($statExport, self::MEDIA_COUNT, 0),
532*04fd306cSNickeau            "internal_count" => self::getAndUnset($statExport, self::INTERNAL_MEDIA_COUNT, 0),
533*04fd306cSNickeau            "internal_broken_count" => self::getAndUnset($statExport, self::INTERNAL_BROKEN_MEDIA_COUNT, 0),
534*04fd306cSNickeau            "external_count" => self::getAndUnset($statExport, self::EXTERNAL_MEDIA_COUNT, 0)
535e8b2ff59SNickeau        ];
536e8b2ff59SNickeau        $statExport['media'] = $mediasStats;
537e8b2ff59SNickeau
538e8b2ff59SNickeau        /**
539007225e5Sgerardnico         * Changes, the more changes the better
540007225e5Sgerardnico         */
541*04fd306cSNickeau        $qualityScores[self::EDITS_COUNT] = $statExport[self::EDITS_COUNT] * $this->getConf(self::CONF_QUALITY_SCORE_CHANGES_FACTOR, 0.25);
542007225e5Sgerardnico
543007225e5Sgerardnico
544007225e5Sgerardnico        /**
545007225e5Sgerardnico         * Quality Score
546007225e5Sgerardnico         */
547007225e5Sgerardnico        ksort($qualityScores);
548007225e5Sgerardnico        $qualityScoring = array();
54908ca4f85Sgerardnico        $qualityScoring[self::SCORE] = array_sum($qualityScores);
550007225e5Sgerardnico        $qualityScoring["scores"] = $qualityScores;
551007225e5Sgerardnico
552007225e5Sgerardnico
553007225e5Sgerardnico        /**
554007225e5Sgerardnico         * The rule that if broken will set the quality level to low
555007225e5Sgerardnico         */
556007225e5Sgerardnico        $brokenRules = array();
557007225e5Sgerardnico        foreach ($ruleResults as $ruleName => $ruleResult) {
558007225e5Sgerardnico            if ($ruleResult == self::FAILED) {
559007225e5Sgerardnico                $brokenRules[] = $ruleName;
560007225e5Sgerardnico            }
561007225e5Sgerardnico        }
562007225e5Sgerardnico        $ruleErrorCount = sizeof($brokenRules);
563007225e5Sgerardnico        if ($ruleErrorCount > 0) {
564007225e5Sgerardnico            $qualityResult = $ruleErrorCount . " quality rules errors";
565007225e5Sgerardnico        } else {
566007225e5Sgerardnico            $qualityResult = "All quality rules passed";
567007225e5Sgerardnico        }
568007225e5Sgerardnico
569007225e5Sgerardnico        /**
570fa5961eaSgerardnico         * Low level Computation
571007225e5Sgerardnico         */
572007225e5Sgerardnico        $mandatoryRules = preg_split("/,/", $this->getConf(self::CONF_MANDATORY_QUALITY_RULES));
573007225e5Sgerardnico        $mandatoryRulesBroken = [];
574007225e5Sgerardnico        foreach ($mandatoryRules as $lowLevelRule) {
575007225e5Sgerardnico            if (in_array($lowLevelRule, $brokenRules)) {
576007225e5Sgerardnico                $mandatoryRulesBroken[] = $lowLevelRule;
577007225e5Sgerardnico            }
578007225e5Sgerardnico        }
579fa5961eaSgerardnico        /**
580c3437056SNickeau         * Low Level
581fa5961eaSgerardnico         */
582007225e5Sgerardnico        $lowLevel = false;
58385e82846SNickeau        $brokenRulesCount = sizeof($mandatoryRulesBroken);
58485e82846SNickeau        if ($brokenRulesCount > 0) {
585007225e5Sgerardnico            $lowLevel = true;
58685e82846SNickeau            $quality["message"] = "$brokenRulesCount mandatory rules broken.";
58785e82846SNickeau        } else {
58885e82846SNickeau            $quality["message"] = "No mandatory rules broken";
589007225e5Sgerardnico        }
590*04fd306cSNickeau        if ($this->page->isSlot()) {
591c3437056SNickeau            $lowLevel = false;
5929b9e6d1fSgerardnico        }
593*04fd306cSNickeau        try {
594c3437056SNickeau            $this->page->setLowQualityIndicatorCalculation($lowLevel);
595*04fd306cSNickeau        } catch (ExceptionCompile $e) {
596*04fd306cSNickeau            LogUtility::msg("An error has occurred while saving the low quality level. Error: {$e->getMessage()}");
597*04fd306cSNickeau        }
598007225e5Sgerardnico
599007225e5Sgerardnico        /**
600007225e5Sgerardnico         * Building the quality object in order
601007225e5Sgerardnico         */
602*04fd306cSNickeau        $quality[self::LOW] = $lowLevel;
603007225e5Sgerardnico        if (sizeof($mandatoryRulesBroken) > 0) {
604007225e5Sgerardnico            ksort($mandatoryRulesBroken);
605*04fd306cSNickeau            $quality[self::FAILED_MANDATORY_RULES] = $mandatoryRulesBroken;
606007225e5Sgerardnico        }
60708ca4f85Sgerardnico        $quality[self::SCORING] = $qualityScoring;
608*04fd306cSNickeau        $quality[self::RULES][self::RESULT] = $qualityResult;
609007225e5Sgerardnico        if (!empty($ruleInfo)) {
610*04fd306cSNickeau            $quality[self::RULES]["info"] = $ruleInfo;
611007225e5Sgerardnico        }
612007225e5Sgerardnico
613007225e5Sgerardnico        ksort($ruleResults);
614*04fd306cSNickeau        $quality[self::RULES][self::DETAILS] = $ruleResults;
615007225e5Sgerardnico
616007225e5Sgerardnico        /**
617007225e5Sgerardnico         * Metadata
618007225e5Sgerardnico         */
619*04fd306cSNickeau        try {
620*04fd306cSNickeau            $requestedPage = MarkupPath::createPageFromExecutingId();
621*04fd306cSNickeau        } catch (ExceptionCompile $e) {
622*04fd306cSNickeau            LogUtility::msg("The global ID is unknown, we can't find the requested page. Analytics was stopped");
623*04fd306cSNickeau            return;
624*04fd306cSNickeau        }
625*04fd306cSNickeau        $meta = $requestedPage->getMetadataForRendering();
62637748cd8SNickeau        foreach ($meta as $key => $value) {
62737748cd8SNickeau            /**
62837748cd8SNickeau             * The metadata may have been set
62937748cd8SNickeau             * by frontmatter
63037748cd8SNickeau             */
63137748cd8SNickeau            if (!isset($this->metadata[$key])) {
63237748cd8SNickeau                $this->metadata[$key] = $value;
633*04fd306cSNickeau                if ($key === PageH1::getName()) {
634*04fd306cSNickeau                    $this->metadata[PageH1::H1_PARSED] = MetadataDokuWikiStore::getOrCreateFromResource($requestedPage)->getFromName(PageH1::H1_PARSED);
635*04fd306cSNickeau                }
636c42a1196Sgerardnico            }
63737748cd8SNickeau        }
638007225e5Sgerardnico
639007225e5Sgerardnico
640007225e5Sgerardnico        /**
641007225e5Sgerardnico         * Building the Top JSON in order
642007225e5Sgerardnico         */
6432c067407Sgerardnico        $finalStats = array();
644c42a1196Sgerardnico        $finalStats["date"] = date('Y-m-d H:i:s', time());
64537748cd8SNickeau        ksort($this->metadata);
646*04fd306cSNickeau        $finalStats[self::METADATA] = $this->metadata;
647007225e5Sgerardnico        ksort($statExport);
648*04fd306cSNickeau        $finalStats[self::STATISTICS] = $statExport;
649*04fd306cSNickeau        $finalStats[self::QUALITY] = $quality; // Quality after the sort to get them at the end
650007225e5Sgerardnico
651007225e5Sgerardnico
652007225e5Sgerardnico        /**
653007225e5Sgerardnico         * The result can be seen with
654007225e5Sgerardnico         * doku.php?id=somepage&do=export_combo_analysis
6557c33ecc6Sgerardnico         *
6567c33ecc6Sgerardnico         * Set the header temporarily for the export.php file
65785e82846SNickeau         *
65885e82846SNickeau         * The mode in the export is
659007225e5Sgerardnico         */
66085e82846SNickeau        $mode = "combo_" . $this->getPluginComponent();
6617c33ecc6Sgerardnico        p_set_metadata(
662*04fd306cSNickeau            $requestedPage->getPageId(),
66385e82846SNickeau            array("format" => array($mode => array("Content-Type" => 'application/json'))),
6647c33ecc6Sgerardnico            false,
665c3437056SNickeau            false // Persistence is needed because there is a cache
6667c33ecc6Sgerardnico        );
6672c067407Sgerardnico        $json_encoded = json_encode($finalStats, JSON_PRETTY_PRINT);
668007225e5Sgerardnico
669*04fd306cSNickeau        $this->doc = $json_encoded;
670007225e5Sgerardnico
671007225e5Sgerardnico    }
672007225e5Sgerardnico
673007225e5Sgerardnico    /**
674007225e5Sgerardnico     */
675007225e5Sgerardnico    public function getFormat()
676007225e5Sgerardnico    {
677531e725cSNickeau        return self::RENDERER_FORMAT;
678007225e5Sgerardnico    }
679007225e5Sgerardnico
680007225e5Sgerardnico
681007225e5Sgerardnico    public function header($text, $level, $pos)
682007225e5Sgerardnico    {
683*04fd306cSNickeau        if (!array_key_exists(self::HEADING_COUNT, $this->stats)) {
684*04fd306cSNickeau            $this->stats[self::HEADING_COUNT] = [];
685ebdc69ceSgerardnico        }
686ebdc69ceSgerardnico        $heading = 'h' . $level;
687ebdc69ceSgerardnico        if (!array_key_exists(
688ebdc69ceSgerardnico            $heading,
689*04fd306cSNickeau            $this->stats[self::HEADING_COUNT])) {
690*04fd306cSNickeau            $this->stats[self::HEADING_COUNT][$heading] = 0;
691ebdc69ceSgerardnico        }
692*04fd306cSNickeau        $this->stats[self::HEADING_COUNT][$heading]++;
693ebdc69ceSgerardnico
694007225e5Sgerardnico        $this->headerId++;
695*04fd306cSNickeau        $this->stats[self::HEADER_POSITION][$this->headerId] = $heading;
696ebdc69ceSgerardnico
697ebdc69ceSgerardnico        /**
698ebdc69ceSgerardnico         * Store the level of each heading
699ebdc69ceSgerardnico         * They should only go from low to highest value
700ebdc69ceSgerardnico         * for a good outline
701ebdc69ceSgerardnico         */
702*04fd306cSNickeau        if (!array_key_exists(self::HEADING_COUNT, $this->stats)) {
703ebdc69ceSgerardnico            $this->stats[self::HEADER_STRUCT] = [];
704ebdc69ceSgerardnico        }
705ebdc69ceSgerardnico        $this->stats[self::HEADER_STRUCT][] = $level;
706007225e5Sgerardnico
707007225e5Sgerardnico    }
708007225e5Sgerardnico
709007225e5Sgerardnico    public function smiley($smiley)
710007225e5Sgerardnico    {
711007225e5Sgerardnico        if ($smiley == 'FIXME') $this->stats[self::FIXME]++;
712007225e5Sgerardnico    }
713007225e5Sgerardnico
714007225e5Sgerardnico    public function linebreak()
715007225e5Sgerardnico    {
716007225e5Sgerardnico        if (!$this->tableopen) {
717007225e5Sgerardnico            $this->stats['linebreak']++;
718007225e5Sgerardnico        }
719007225e5Sgerardnico    }
720007225e5Sgerardnico
721007225e5Sgerardnico    public function table_open($maxcols = null, $numrows = null, $pos = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
722007225e5Sgerardnico    {
723007225e5Sgerardnico        $this->tableopen = true;
724007225e5Sgerardnico    }
725007225e5Sgerardnico
726007225e5Sgerardnico    public function table_close($pos = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
727007225e5Sgerardnico    {
728007225e5Sgerardnico        $this->tableopen = false;
729007225e5Sgerardnico    }
730007225e5Sgerardnico
731007225e5Sgerardnico    public function hr()
732007225e5Sgerardnico    {
733007225e5Sgerardnico        $this->stats['hr']++;
734007225e5Sgerardnico    }
735007225e5Sgerardnico
736007225e5Sgerardnico    public function quote_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
737007225e5Sgerardnico    {
738007225e5Sgerardnico        $this->stats['quote_count']++;
739007225e5Sgerardnico        $this->quotelevel++;
740007225e5Sgerardnico        $this->stats['quote_nest'] = max($this->quotelevel, $this->stats['quote_nest']);
741007225e5Sgerardnico    }
742007225e5Sgerardnico
743007225e5Sgerardnico    public function quote_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
744007225e5Sgerardnico    {
745007225e5Sgerardnico        $this->quotelevel--;
746007225e5Sgerardnico    }
747007225e5Sgerardnico
748007225e5Sgerardnico    public function strong_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
749007225e5Sgerardnico    {
750007225e5Sgerardnico        $this->formattingBracket++;
751007225e5Sgerardnico    }
752007225e5Sgerardnico
753007225e5Sgerardnico    public function strong_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
754007225e5Sgerardnico    {
755007225e5Sgerardnico        $this->formattingBracket--;
756007225e5Sgerardnico    }
757007225e5Sgerardnico
758007225e5Sgerardnico    public function emphasis_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
759007225e5Sgerardnico    {
760007225e5Sgerardnico        $this->formattingBracket++;
761007225e5Sgerardnico    }
762007225e5Sgerardnico
763007225e5Sgerardnico    public function emphasis_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
764007225e5Sgerardnico    {
765007225e5Sgerardnico        $this->formattingBracket--;
766007225e5Sgerardnico    }
767007225e5Sgerardnico
768007225e5Sgerardnico    public function underline_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
769007225e5Sgerardnico    {
770007225e5Sgerardnico        $this->formattingBracket++;
771007225e5Sgerardnico    }
772007225e5Sgerardnico
773*04fd306cSNickeau    public function addToDescription($text)
774*04fd306cSNickeau    {
7754cadd4f8SNickeau
7764cadd4f8SNickeau    }
7774cadd4f8SNickeau
778007225e5Sgerardnico    public function underline_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
779007225e5Sgerardnico    {
780007225e5Sgerardnico        $this->formattingBracket--;
781007225e5Sgerardnico    }
782007225e5Sgerardnico
783007225e5Sgerardnico    public function cdata($text)
784007225e5Sgerardnico    {
785007225e5Sgerardnico
786007225e5Sgerardnico        /**
787007225e5Sgerardnico         * It seems that you receive cdata
788007225e5Sgerardnico         * when emphasis_open / underline_open / strong_open
789007225e5Sgerardnico         * Stats are not for them
790007225e5Sgerardnico         */
791007225e5Sgerardnico        if (!$this->formattingBracket) return;
792007225e5Sgerardnico
793007225e5Sgerardnico        $this->plainTextId++;
794007225e5Sgerardnico
795007225e5Sgerardnico        /**
796007225e5Sgerardnico         * Length
797007225e5Sgerardnico         */
798007225e5Sgerardnico        $len = strlen($text);
799007225e5Sgerardnico        $this->stats[self::PLAINTEXT][$this->plainTextId]['len'] = $len;
800007225e5Sgerardnico
801007225e5Sgerardnico
802007225e5Sgerardnico        /**
803007225e5Sgerardnico         * Multi-formatting
804007225e5Sgerardnico         */
805007225e5Sgerardnico        if ($this->formattingBracket > 1) {
806007225e5Sgerardnico            $numberOfFormats = 1 * ($this->formattingBracket - 1);
807007225e5Sgerardnico            $this->stats[self::PLAINTEXT][$this->plainTextId]['multiformat'] += $numberOfFormats;
808007225e5Sgerardnico        }
809007225e5Sgerardnico
810007225e5Sgerardnico        /**
811007225e5Sgerardnico         * Total
812007225e5Sgerardnico         */
813007225e5Sgerardnico        $this->stats[self::PLAINTEXT][0] += $len;
814007225e5Sgerardnico    }
815007225e5Sgerardnico
816007225e5Sgerardnico    public function internalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null)
817007225e5Sgerardnico    {
818*04fd306cSNickeau        $this->stats[self::INTERNAL_MEDIA_COUNT]++;
819007225e5Sgerardnico    }
820007225e5Sgerardnico
821007225e5Sgerardnico    public function externalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null)
822007225e5Sgerardnico    {
823*04fd306cSNickeau        $this->stats[self::EXTERNAL_MEDIA_COUNT]++;
824007225e5Sgerardnico    }
825007225e5Sgerardnico
826007225e5Sgerardnico    public function reset()
827007225e5Sgerardnico    {
828007225e5Sgerardnico        $this->stats = array();
82937748cd8SNickeau        $this->metadata = array();
830007225e5Sgerardnico        $this->headerId = 0;
831007225e5Sgerardnico    }
832007225e5Sgerardnico
833c3437056SNickeau    public function setAnalyticsMetaForReporting($key, $value)
834007225e5Sgerardnico    {
83537748cd8SNickeau        $this->metadata[$key] = $value;
836007225e5Sgerardnico    }
837007225e5Sgerardnico
838007225e5Sgerardnico
839007225e5Sgerardnico}
840007225e5Sgerardnico
841