xref: /plugin/combo/renderer/analytics.php (revision 4cadd4f8c541149bdda95f080e38a6d4e3a640ca)
1007225e5Sgerardnico<?php
2007225e5Sgerardnico
3007225e5Sgerardnico
4c3437056SNickeauuse ComboStrap\AnalyticsDocument;
5c3437056SNickeauuse ComboStrap\BacklinkCount;
6c3437056SNickeauuse ComboStrap\Canonical;
7*4cadd4f8SNickeauuse ComboStrap\MarkupRef;
8c3437056SNickeauuse ComboStrap\MetadataDbStore;
971f916b9Sgerardnicouse ComboStrap\Page;
10c3437056SNickeauuse ComboStrap\PageTitle;
11c3437056SNickeauuse ComboStrap\PluginUtility;
1237748cd8SNickeauuse ComboStrap\StringUtility;
13007225e5Sgerardnicouse dokuwiki\ChangeLog\PageChangeLog;
14007225e5Sgerardnico
1537748cd8SNickeau
1637748cd8SNickeaurequire_once(__DIR__ . '/../ComboStrap/PluginUtility.php');
17007225e5Sgerardnico
18007225e5Sgerardnico
19007225e5Sgerardnico/**
20007225e5Sgerardnico * A analysis Renderer that exports stats/quality/metadata in a json format
21007225e5Sgerardnico * You can export the data with
22007225e5Sgerardnico * doku.php?id=somepage&do=export_combo_analytics
23007225e5Sgerardnico */
24007225e5Sgerardnicoclass renderer_plugin_combo_analytics extends Doku_Renderer
25007225e5Sgerardnico{
267c33ecc6Sgerardnico
27007225e5Sgerardnico    const PLAINTEXT = 'formatted';
28007225e5Sgerardnico    const RESULT = "result";
29007225e5Sgerardnico    const DESCRIPTION = "description";
30007225e5Sgerardnico    const PASSED = "Passed";
31007225e5Sgerardnico    const FAILED = "Failed";
32007225e5Sgerardnico    const FIXME = 'fixme';
33007225e5Sgerardnico
34007225e5Sgerardnico    /**
35007225e5Sgerardnico     * Rules key
36007225e5Sgerardnico     */
37007225e5Sgerardnico    const RULE_WORDS_MINIMAL = 'words_min';
38007225e5Sgerardnico    const RULE_OUTLINE_STRUCTURE = "outline_structure";
39007225e5Sgerardnico    const RULE_INTERNAL_BACKLINKS_MIN = 'internal_backlinks_min';
40007225e5Sgerardnico    const RULE_WORDS_MAXIMAL = "words_max";
41007225e5Sgerardnico    const RULE_AVERAGE_WORDS_BY_SECTION_MIN = 'words_by_section_avg_min';
42007225e5Sgerardnico    const RULE_AVERAGE_WORDS_BY_SECTION_MAX = 'words_by_section_avg_max';
43007225e5Sgerardnico    const RULE_INTERNAL_LINKS_MIN = 'internal_links_min';
44007225e5Sgerardnico    const RULE_INTERNAL_BROKEN_LINKS_MAX = 'internal_links_broken_max';
45007225e5Sgerardnico    const RULE_DESCRIPTION_PRESENT = 'description_present';
46007225e5Sgerardnico    const RULE_FIXME = "fixme_min";
47007225e5Sgerardnico    const RULE_TITLE_PRESENT = "title_present";
48007225e5Sgerardnico    const RULE_CANONICAL_PRESENT = "canonical_present";
49aa3cb38fSgerardnico    const QUALITY_RULES = [
50aa3cb38fSgerardnico        self::RULE_CANONICAL_PRESENT,
51aa3cb38fSgerardnico        self::RULE_DESCRIPTION_PRESENT,
52aa3cb38fSgerardnico        self::RULE_FIXME,
53aa3cb38fSgerardnico        self::RULE_INTERNAL_BACKLINKS_MIN,
54aa3cb38fSgerardnico        self::RULE_INTERNAL_BROKEN_LINKS_MAX,
55aa3cb38fSgerardnico        self::RULE_INTERNAL_LINKS_MIN,
56aa3cb38fSgerardnico        self::RULE_OUTLINE_STRUCTURE,
57aa3cb38fSgerardnico        self::RULE_TITLE_PRESENT,
58aa3cb38fSgerardnico        self::RULE_WORDS_MINIMAL,
59aa3cb38fSgerardnico        self::RULE_WORDS_MAXIMAL,
60aa3cb38fSgerardnico        self::RULE_AVERAGE_WORDS_BY_SECTION_MIN,
61aa3cb38fSgerardnico        self::RULE_AVERAGE_WORDS_BY_SECTION_MAX
62aa3cb38fSgerardnico    ];
63007225e5Sgerardnico
64007225e5Sgerardnico    /**
65007225e5Sgerardnico     * The default man
66007225e5Sgerardnico     */
67007225e5Sgerardnico    const CONF_MANDATORY_QUALITY_RULES_DEFAULT_VALUE = [
68007225e5Sgerardnico        self::RULE_WORDS_MINIMAL,
69007225e5Sgerardnico        self::RULE_INTERNAL_BACKLINKS_MIN,
70007225e5Sgerardnico        self::RULE_INTERNAL_LINKS_MIN
71007225e5Sgerardnico    ];
72007225e5Sgerardnico    const CONF_MANDATORY_QUALITY_RULES = "mandatoryQualityRules";
73007225e5Sgerardnico
74007225e5Sgerardnico    /**
75007225e5Sgerardnico     * Quality Score factors
76007225e5Sgerardnico     * They are used to calculate the score
77007225e5Sgerardnico     */
78007225e5Sgerardnico    const CONF_QUALITY_SCORE_INTERNAL_BACKLINK_FACTOR = 'qualityScoreInternalBacklinksFactor';
79007225e5Sgerardnico    const CONF_QUALITY_SCORE_INTERNAL_LINK_FACTOR = 'qualityScoreInternalLinksFactor';
80007225e5Sgerardnico    const CONF_QUALITY_SCORE_TITLE_PRESENT = 'qualityScoreTitlePresent';
81007225e5Sgerardnico    const CONF_QUALITY_SCORE_CORRECT_HEADER_STRUCTURE = 'qualityScoreCorrectOutline';
82007225e5Sgerardnico    const CONF_QUALITY_SCORE_CORRECT_CONTENT = 'qualityScoreCorrectContentLength';
83007225e5Sgerardnico    const CONF_QUALITY_SCORE_NO_FIXME = 'qualityScoreNoFixMe';
84007225e5Sgerardnico    const CONF_QUALITY_SCORE_CORRECT_WORD_SECTION_AVERAGE = 'qualityScoreCorrectWordSectionAvg';
85007225e5Sgerardnico    const CONF_QUALITY_SCORE_INTERNAL_LINK_BROKEN_FACTOR = 'qualityScoreNoBrokenLinks';
86007225e5Sgerardnico    const CONF_QUALITY_SCORE_CHANGES_FACTOR = 'qualityScoreChangesFactor';
87007225e5Sgerardnico    const CONF_QUALITY_SCORE_DESCRIPTION_PRESENT = 'qualityScoreDescriptionPresent';
88007225e5Sgerardnico    const CONF_QUALITY_SCORE_CANONICAL_PRESENT = 'qualityScoreCanonicalPresent';
8908ca4f85Sgerardnico    const SCORING = "scoring";
9008ca4f85Sgerardnico    const SCORE = "score";
91ebdc69ceSgerardnico    const HEADER_STRUCT = 'header_struct';
92531e725cSNickeau    const RENDERER_NAME_MODE = "combo_" . renderer_plugin_combo_analytics::RENDERER_FORMAT;
93c3437056SNickeau
94531e725cSNickeau    /**
95531e725cSNickeau     * The format returned by the renderer
96531e725cSNickeau     */
97531e725cSNickeau    const RENDERER_FORMAT = "analytics";
98007225e5Sgerardnico
99aa3cb38fSgerardnico
100007225e5Sgerardnico    /**
101007225e5Sgerardnico     * The processing data
102007225e5Sgerardnico     * that should be {@link  renderer_plugin_combo_analysis::reset()}
103007225e5Sgerardnico     */
104007225e5Sgerardnico    public $stats = array(); // the stats
10537748cd8SNickeau    protected $metadata = array(); // the metadata in frontmatter
106007225e5Sgerardnico    protected $headerId = 0; // the id of the header on the page (first, second, ...)
107007225e5Sgerardnico
108007225e5Sgerardnico    /**
109007225e5Sgerardnico     * Don't known this variable ?
110007225e5Sgerardnico     */
111007225e5Sgerardnico    protected $quotelevel = 0;
112007225e5Sgerardnico    protected $formattingBracket = 0;
113007225e5Sgerardnico    protected $tableopen = false;
114007225e5Sgerardnico    private $plainTextId = 0;
1152c067407Sgerardnico    /**
1162c067407Sgerardnico     * @var Page
1172c067407Sgerardnico     */
1182c067407Sgerardnico    private $page;
1192c067407Sgerardnico
120e8b2ff59SNickeau    /**
121e8b2ff59SNickeau     * Get and unset a value from an array
122e8b2ff59SNickeau     * @param array $array
123e8b2ff59SNickeau     * @param $key
124e8b2ff59SNickeau     * @param $default
125e8b2ff59SNickeau     * @return mixed
126e8b2ff59SNickeau     */
127e8b2ff59SNickeau    private static function getAndUnset(array &$array, $key, $default)
128e8b2ff59SNickeau    {
129e8b2ff59SNickeau        if (isset($array[$key])) {
130e8b2ff59SNickeau            $value = $array[$key];
131e8b2ff59SNickeau            unset($array[$key]);
132e8b2ff59SNickeau            return $value;
133e8b2ff59SNickeau        }
134e8b2ff59SNickeau        return $default;
135e8b2ff59SNickeau
136e8b2ff59SNickeau    }
137e8b2ff59SNickeau
1382c067407Sgerardnico    public function document_start()
1392c067407Sgerardnico    {
1407c33ecc6Sgerardnico        $this->reset();
141c3437056SNickeau        $this->page = Page::createPageFromGlobalDokuwikiId();
1422c067407Sgerardnico
1432c067407Sgerardnico    }
144007225e5Sgerardnico
145007225e5Sgerardnico
146007225e5Sgerardnico    /**
147007225e5Sgerardnico     * Here the score is calculated
148c3437056SNickeau     * @throws \ComboStrap\ExceptionCombo
149007225e5Sgerardnico     */
150007225e5Sgerardnico    public function document_end() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
151007225e5Sgerardnico    {
152007225e5Sgerardnico        /**
153f3748b38Sgerardnico         * The exported object
154f3748b38Sgerardnico         */
155f3748b38Sgerardnico        $statExport = $this->stats;
156f3748b38Sgerardnico
157f3748b38Sgerardnico        /**
158007225e5Sgerardnico         * The metadata
159007225e5Sgerardnico         */
160007225e5Sgerardnico        global $ID;
161fa5961eaSgerardnico        $dokuWikiMetadata = p_get_metadata($ID);
162007225e5Sgerardnico
163007225e5Sgerardnico        /**
164f3748b38Sgerardnico         * Edit author stats
165f3748b38Sgerardnico         */
166f3748b38Sgerardnico        $changelog = new PageChangeLog($ID);
167f3748b38Sgerardnico        $revs = $changelog->getRevisions(0, 10000);
168fa5961eaSgerardnico        array_push($revs, $dokuWikiMetadata['last_change']['date']);
169c3437056SNickeau        $statExport[AnalyticsDocument::EDITS_COUNT] = count($revs);
170f3748b38Sgerardnico        foreach ($revs as $rev) {
1712128d419Sgerardnico
172ebdc69ceSgerardnico
173ebdc69ceSgerardnico            /**
174ebdc69ceSgerardnico             * Init the authors array
175ebdc69ceSgerardnico             */
176ebdc69ceSgerardnico            if (!array_key_exists('authors', $statExport)) {
177ebdc69ceSgerardnico                $statExport['authors'] = [];
178f3748b38Sgerardnico            }
179ebdc69ceSgerardnico            /**
180ebdc69ceSgerardnico             * Analytics by users
181ebdc69ceSgerardnico             */
1822128d419Sgerardnico            $info = $changelog->getRevisionInfo($rev);
1832128d419Sgerardnico            if (is_array($info)) {
184ebdc69ceSgerardnico                $user = "*";
185ebdc69ceSgerardnico                if (array_key_exists('user', $info)) {
186ebdc69ceSgerardnico                    $user = $info['user'];
187ebdc69ceSgerardnico                }
188ebdc69ceSgerardnico                if (!array_key_exists('authors', $statExport['authors'])) {
189ebdc69ceSgerardnico                    $statExport['authors'][$user] = 0;
190ebdc69ceSgerardnico                }
191ebdc69ceSgerardnico                $statExport['authors'][$user] += 1;
192f3748b38Sgerardnico            }
1932128d419Sgerardnico        }
194f3748b38Sgerardnico
195f3748b38Sgerardnico        /**
196007225e5Sgerardnico         * Word and chars count
197007225e5Sgerardnico         * The word count does not take into account
198007225e5Sgerardnico         * words with non-words characters such as < =
199007225e5Sgerardnico         * Therefore the node and attribute are not taken in the count
200007225e5Sgerardnico         */
201007225e5Sgerardnico        $text = rawWiki($ID);
202c3437056SNickeau        $statExport[AnalyticsDocument::CHAR_COUNT] = strlen($text);
203c3437056SNickeau        $statExport[AnalyticsDocument::WORD_COUNT] = StringUtility::getWordCount($text);
204007225e5Sgerardnico
205007225e5Sgerardnico
206007225e5Sgerardnico        /**
207007225e5Sgerardnico         * Internal link distance summary calculation
208007225e5Sgerardnico         */
209c3437056SNickeau        if (array_key_exists(AnalyticsDocument::INTERNAL_LINK_DISTANCE, $statExport)) {
210c3437056SNickeau            $linkLengths = $statExport[AnalyticsDocument::INTERNAL_LINK_DISTANCE];
211c3437056SNickeau            unset($statExport[AnalyticsDocument::INTERNAL_LINK_DISTANCE]);
212007225e5Sgerardnico            $countBacklinks = count($linkLengths);
213c3437056SNickeau            $statExport[AnalyticsDocument::INTERNAL_LINK_DISTANCE]['avg'] = null;
214c3437056SNickeau            $statExport[AnalyticsDocument::INTERNAL_LINK_DISTANCE]['max'] = null;
215c3437056SNickeau            $statExport[AnalyticsDocument::INTERNAL_LINK_DISTANCE]['min'] = null;
216007225e5Sgerardnico            if ($countBacklinks > 0) {
217c3437056SNickeau                $statExport[AnalyticsDocument::INTERNAL_LINK_DISTANCE]['avg'] = array_sum($linkLengths) / $countBacklinks;
218c3437056SNickeau                $statExport[AnalyticsDocument::INTERNAL_LINK_DISTANCE]['max'] = max($linkLengths);
219c3437056SNickeau                $statExport[AnalyticsDocument::INTERNAL_LINK_DISTANCE]['min'] = min($linkLengths);
220007225e5Sgerardnico            }
221007225e5Sgerardnico        }
222007225e5Sgerardnico
223007225e5Sgerardnico        /**
224007225e5Sgerardnico         * Quality Report / Rules
225007225e5Sgerardnico         */
226007225e5Sgerardnico        // The array that hold the results of the quality rules
227007225e5Sgerardnico        $ruleResults = array();
228007225e5Sgerardnico        // The array that hold the quality score details
229007225e5Sgerardnico        $qualityScores = array();
230007225e5Sgerardnico
231007225e5Sgerardnico
232007225e5Sgerardnico        /**
233007225e5Sgerardnico         * No fixme
234007225e5Sgerardnico         */
235ebdc69ceSgerardnico        if (array_key_exists(self::FIXME, $this->stats)) {
236007225e5Sgerardnico            $fixmeCount = $this->stats[self::FIXME];
237007225e5Sgerardnico            $statExport[self::FIXME] = $fixmeCount == null ? 0 : $fixmeCount;
238007225e5Sgerardnico            if ($fixmeCount != 0) {
239007225e5Sgerardnico                $ruleResults[self::RULE_FIXME] = self::FAILED;
240007225e5Sgerardnico                $qualityScores['no_' . self::FIXME] = 0;
241007225e5Sgerardnico            } else {
242007225e5Sgerardnico                $ruleResults[self::RULE_FIXME] = self::PASSED;
2437c33ecc6Sgerardnico                $qualityScores['no_' . self::FIXME] = $this->getConf(self::CONF_QUALITY_SCORE_NO_FIXME, 1);
244007225e5Sgerardnico            }
245ebdc69ceSgerardnico        }
246007225e5Sgerardnico
247007225e5Sgerardnico        /**
248007225e5Sgerardnico         * A title should be present
249007225e5Sgerardnico         */
25008ca4f85Sgerardnico        $titleScore = $this->getConf(self::CONF_QUALITY_SCORE_TITLE_PRESENT, 10);
251c3437056SNickeau        if (empty($this->metadata[PageTitle::TITLE])) {
252007225e5Sgerardnico            $ruleResults[self::RULE_TITLE_PRESENT] = self::FAILED;
253c3437056SNickeau            $ruleInfo[self::RULE_TITLE_PRESENT] = "Add a title for {$titleScore} points";
254c3437056SNickeau            $this->metadata[PageTitle::TITLE] = $dokuWikiMetadata[PageTitle::TITLE];
255007225e5Sgerardnico            $qualityScores[self::RULE_TITLE_PRESENT] = 0;
256007225e5Sgerardnico        } else {
2577c33ecc6Sgerardnico            $qualityScores[self::RULE_TITLE_PRESENT] = $titleScore;
258007225e5Sgerardnico            $ruleResults[self::RULE_TITLE_PRESENT] = self::PASSED;
259007225e5Sgerardnico        }
260007225e5Sgerardnico
261007225e5Sgerardnico        /**
262007225e5Sgerardnico         * A description should be present
263007225e5Sgerardnico         */
26408ca4f85Sgerardnico        $descScore = $this->getConf(self::CONF_QUALITY_SCORE_DESCRIPTION_PRESENT, 8);
26537748cd8SNickeau        if (empty($this->metadata[self::DESCRIPTION])) {
266007225e5Sgerardnico            $ruleResults[self::RULE_DESCRIPTION_PRESENT] = self::FAILED;
267c3437056SNickeau            $ruleInfo[self::RULE_DESCRIPTION_PRESENT] = "Add a description for {$descScore} points";
26837748cd8SNickeau            $this->metadata[self::DESCRIPTION] = $dokuWikiMetadata[self::DESCRIPTION]["abstract"];
269007225e5Sgerardnico            $qualityScores[self::RULE_DESCRIPTION_PRESENT] = 0;
270007225e5Sgerardnico        } else {
2717c33ecc6Sgerardnico            $qualityScores[self::RULE_DESCRIPTION_PRESENT] = $descScore;
272007225e5Sgerardnico            $ruleResults[self::RULE_DESCRIPTION_PRESENT] = self::PASSED;
273007225e5Sgerardnico        }
274007225e5Sgerardnico
275007225e5Sgerardnico        /**
276007225e5Sgerardnico         * A canonical should be present
277007225e5Sgerardnico         */
27808ca4f85Sgerardnico        $canonicalScore = $this->getConf(self::CONF_QUALITY_SCORE_CANONICAL_PRESENT, 5);
279c3437056SNickeau        if (empty($this->metadata[Canonical::PROPERTY_NAME])) {
280f3748b38Sgerardnico            global $conf;
281f3748b38Sgerardnico            $root = $conf['start'];
282*4cadd4f8SNickeau            if ($ID !== $root) {
283007225e5Sgerardnico                $qualityScores[self::RULE_CANONICAL_PRESENT] = 0;
284007225e5Sgerardnico                $ruleResults[self::RULE_CANONICAL_PRESENT] = self::FAILED;
285c3437056SNickeau                // no link to the documentation because we don't want any html in the json
286c3437056SNickeau                $ruleInfo[self::RULE_CANONICAL_PRESENT] = "Add a canonical for {$canonicalScore} points";
287f3748b38Sgerardnico            }
288007225e5Sgerardnico        } else {
2897c33ecc6Sgerardnico            $qualityScores[self::RULE_CANONICAL_PRESENT] = $canonicalScore;
290007225e5Sgerardnico            $ruleResults[self::RULE_CANONICAL_PRESENT] = self::PASSED;
291007225e5Sgerardnico        }
292007225e5Sgerardnico
293007225e5Sgerardnico        /**
294007225e5Sgerardnico         * Outline / Header structure
295007225e5Sgerardnico         */
296007225e5Sgerardnico        $treeError = 0;
297007225e5Sgerardnico        $headersCount = 0;
298c3437056SNickeau        if (array_key_exists(AnalyticsDocument::HEADER_POSITION, $this->stats)) {
299c3437056SNickeau            $headersCount = count($this->stats[AnalyticsDocument::HEADER_POSITION]);
300c3437056SNickeau            unset($statExport[AnalyticsDocument::HEADER_POSITION]);
301007225e5Sgerardnico            for ($i = 1; $i < $headersCount; $i++) {
302ebdc69ceSgerardnico                $currentHeaderLevel = $this->stats[self::HEADER_STRUCT][$i];
303ebdc69ceSgerardnico                $previousHeaderLevel = $this->stats[self::HEADER_STRUCT][$i - 1];
304007225e5Sgerardnico                if ($currentHeaderLevel - $previousHeaderLevel > 1) {
305007225e5Sgerardnico                    $treeError += 1;
306007225e5Sgerardnico                    $ruleInfo[self::RULE_OUTLINE_STRUCTURE] = "The " . $i . " header (h" . $currentHeaderLevel . ") has a level bigger than its precedent (" . $previousHeaderLevel . ")";
307007225e5Sgerardnico                }
308007225e5Sgerardnico            }
309ebdc69ceSgerardnico            unset($statExport[self::HEADER_STRUCT]);
310007225e5Sgerardnico        }
311eee76a3dSgerardnico        $outlinePoints = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_HEADER_STRUCTURE, 3);
312007225e5Sgerardnico        if ($treeError > 0 || $headersCount == 0) {
313007225e5Sgerardnico            $qualityScores['correct_outline'] = 0;
314007225e5Sgerardnico            $ruleResults[self::RULE_OUTLINE_STRUCTURE] = self::FAILED;
315007225e5Sgerardnico            if ($headersCount == 0) {
316eee76a3dSgerardnico                $ruleInfo[self::RULE_OUTLINE_STRUCTURE] = "Add headings to create a document outline for {$outlinePoints} points";
317007225e5Sgerardnico            }
318007225e5Sgerardnico        } else {
319eee76a3dSgerardnico            $qualityScores['correct_outline'] = $outlinePoints;
320007225e5Sgerardnico            $ruleResults[self::RULE_OUTLINE_STRUCTURE] = self::PASSED;
321007225e5Sgerardnico        }
322007225e5Sgerardnico
323007225e5Sgerardnico
324007225e5Sgerardnico        /**
325007225e5Sgerardnico         * Document length
326007225e5Sgerardnico         */
327007225e5Sgerardnico        $minimalWordCount = 50;
328007225e5Sgerardnico        $maximalWordCount = 1500;
329007225e5Sgerardnico        $correctContentLength = true;
33008ca4f85Sgerardnico        $correctLengthScore = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_CONTENT, 10);
331c3437056SNickeau        $missingWords = $minimalWordCount - $statExport[AnalyticsDocument::WORD_COUNT];
33208ca4f85Sgerardnico        if ($missingWords > 0) {
333007225e5Sgerardnico            $ruleResults[self::RULE_WORDS_MINIMAL] = self::FAILED;
334007225e5Sgerardnico            $correctContentLength = false;
33508ca4f85Sgerardnico            $ruleInfo[self::RULE_WORDS_MINIMAL] = "Add {$missingWords} words to get {$correctLengthScore} points";
336007225e5Sgerardnico        } else {
337007225e5Sgerardnico            $ruleResults[self::RULE_WORDS_MINIMAL] = self::PASSED;
338007225e5Sgerardnico        }
339c3437056SNickeau        $tooMuchWords = $statExport[AnalyticsDocument::WORD_COUNT] - $maximalWordCount;
34008ca4f85Sgerardnico        if ($tooMuchWords > 0) {
341007225e5Sgerardnico            $ruleResults[self::RULE_WORDS_MAXIMAL] = self::FAILED;
34208ca4f85Sgerardnico            $ruleInfo[self::RULE_WORDS_MAXIMAL] = "Delete {$tooMuchWords} words to get {$correctLengthScore} points";
343007225e5Sgerardnico            $correctContentLength = false;
344007225e5Sgerardnico        } else {
345007225e5Sgerardnico            $ruleResults[self::RULE_WORDS_MAXIMAL] = self::PASSED;
346007225e5Sgerardnico        }
347007225e5Sgerardnico        if ($correctContentLength) {
34808ca4f85Sgerardnico            $qualityScores['correct_content_length'] = $correctLengthScore;
349007225e5Sgerardnico        } else {
350007225e5Sgerardnico            $qualityScores['correct_content_length'] = 0;
351007225e5Sgerardnico        }
352007225e5Sgerardnico
353007225e5Sgerardnico
354007225e5Sgerardnico        /**
355007225e5Sgerardnico         * Average Number of words by header section to text ratio
356007225e5Sgerardnico         */
357c3437056SNickeau        $headers = $this->stats[AnalyticsDocument::HEADING_COUNT];
358007225e5Sgerardnico        if ($headers != null) {
359007225e5Sgerardnico            $headerCount = array_sum($headers);
360007225e5Sgerardnico            $headerCount--; // h1 is supposed to have no words
361007225e5Sgerardnico            if ($headerCount > 0) {
362007225e5Sgerardnico
363c3437056SNickeau                $avgWordsCountBySection = round($this->stats[AnalyticsDocument::WORD_COUNT] / $headerCount);
364007225e5Sgerardnico                $statExport['word_section_count']['avg'] = $avgWordsCountBySection;
365007225e5Sgerardnico
366007225e5Sgerardnico                /**
367007225e5Sgerardnico                 * Min words by header section
368007225e5Sgerardnico                 */
369007225e5Sgerardnico                $wordsByHeaderMin = 20;
370007225e5Sgerardnico                /**
371007225e5Sgerardnico                 * Max words by header section
372007225e5Sgerardnico                 */
373007225e5Sgerardnico                $wordsByHeaderMax = 300;
374007225e5Sgerardnico                $correctAverageWordsBySection = true;
375007225e5Sgerardnico                if ($avgWordsCountBySection < $wordsByHeaderMin) {
376007225e5Sgerardnico                    $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = self::FAILED;
377007225e5Sgerardnico                    $correctAverageWordsBySection = false;
37808ca4f85Sgerardnico                    $ruleInfo[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = "The number of words by section is less than {$wordsByHeaderMin}";
379007225e5Sgerardnico                } else {
380007225e5Sgerardnico                    $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = self::PASSED;
381007225e5Sgerardnico                }
382007225e5Sgerardnico                if ($avgWordsCountBySection > $wordsByHeaderMax) {
383007225e5Sgerardnico                    $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = self::FAILED;
384007225e5Sgerardnico                    $correctAverageWordsBySection = false;
385007225e5Sgerardnico                    $ruleInfo[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = "The number of words by section is more than {$wordsByHeaderMax}";
386007225e5Sgerardnico                } else {
387007225e5Sgerardnico                    $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = self::PASSED;
388007225e5Sgerardnico                }
389007225e5Sgerardnico                if ($correctAverageWordsBySection) {
390007225e5Sgerardnico                    $qualityScores['correct_word_avg_by_section'] = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_WORD_SECTION_AVERAGE, 10);
391007225e5Sgerardnico                } else {
392007225e5Sgerardnico                    $qualityScores['correct_word_avg_by_section'] = 0;
393007225e5Sgerardnico                }
394007225e5Sgerardnico
395007225e5Sgerardnico            }
396007225e5Sgerardnico        }
397007225e5Sgerardnico
398007225e5Sgerardnico        /**
399007225e5Sgerardnico         * Internal Backlinks rule
400007225e5Sgerardnico         *
401c3437056SNickeau         * We used the database table to get the backlinks
402c3437056SNickeau         * because the replication is based on it
403c3437056SNickeau         * If the dokuwiki index is not up to date, we may got
404c3437056SNickeau         * inconsistency
405007225e5Sgerardnico         */
406c3437056SNickeau        $countBacklinks = BacklinkCount::createFromResource($this->page)
407c3437056SNickeau            ->setReadStore(MetadataDbStore::class)
408c3437056SNickeau            ->getValueOrDefault();
409c3437056SNickeau        $statExport[BacklinkCount::getPersistentName()] = $countBacklinks;
410d262537cSgerardnico        $backlinkScore = $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_BACKLINK_FACTOR, 1);
411007225e5Sgerardnico        if ($countBacklinks == 0) {
412c3437056SNickeau
413c3437056SNickeau            $qualityScores[BacklinkCount::getPersistentName()] = 0;
414007225e5Sgerardnico            $ruleResults[self::RULE_INTERNAL_BACKLINKS_MIN] = self::FAILED;
415d262537cSgerardnico            $ruleInfo[self::RULE_INTERNAL_BACKLINKS_MIN] = "Add backlinks for {$backlinkScore} point each";
416c3437056SNickeau
417007225e5Sgerardnico        } else {
418d262537cSgerardnico
419c3437056SNickeau            $qualityScores[BacklinkCount::getPersistentName()] = $countBacklinks * $backlinkScore;
420007225e5Sgerardnico            $ruleResults[self::RULE_INTERNAL_BACKLINKS_MIN] = self::PASSED;
421007225e5Sgerardnico        }
422007225e5Sgerardnico
423007225e5Sgerardnico        /**
424007225e5Sgerardnico         * Internal links
425007225e5Sgerardnico         */
426c3437056SNickeau        $internalLinksCount = $this->stats[AnalyticsDocument::INTERNAL_LINK_COUNT];
427d262537cSgerardnico        $internalLinkScore = $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_LINK_FACTOR, 1);
428007225e5Sgerardnico        if ($internalLinksCount == 0) {
429c3437056SNickeau            $qualityScores[AnalyticsDocument::INTERNAL_LINK_COUNT] = 0;
430007225e5Sgerardnico            $ruleResults[self::RULE_INTERNAL_LINKS_MIN] = self::FAILED;
431d262537cSgerardnico            $ruleInfo[self::RULE_INTERNAL_LINKS_MIN] = "Add internal links for {$internalLinkScore} point each";
432007225e5Sgerardnico        } else {
433007225e5Sgerardnico            $ruleResults[self::RULE_INTERNAL_LINKS_MIN] = self::PASSED;
434c3437056SNickeau            $qualityScores[AnalyticsDocument::INTERNAL_LINK_COUNT] = $countBacklinks * $internalLinkScore;
435007225e5Sgerardnico        }
436007225e5Sgerardnico
437007225e5Sgerardnico        /**
438007225e5Sgerardnico         * Broken Links
439007225e5Sgerardnico         */
440d262537cSgerardnico        $brokenLinkScore = $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_LINK_BROKEN_FACTOR, 2);
441ebdc69ceSgerardnico        $brokenLinksCount = 0;
442c3437056SNickeau        if (array_key_exists(AnalyticsDocument::INTERNAL_LINK_BROKEN_COUNT, $this->stats)) {
443c3437056SNickeau            $brokenLinksCount = $this->stats[AnalyticsDocument::INTERNAL_LINK_BROKEN_COUNT];
444ebdc69ceSgerardnico        }
445007225e5Sgerardnico        if ($brokenLinksCount > 2) {
446c3437056SNickeau            $qualityScores['no_' . AnalyticsDocument::INTERNAL_LINK_BROKEN_COUNT] = 0;
447007225e5Sgerardnico            $ruleResults[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = self::FAILED;
448d262537cSgerardnico            $ruleInfo[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = "Delete the {$brokenLinksCount} broken links and add {$brokenLinkScore} points";
449007225e5Sgerardnico        } else {
450c3437056SNickeau            $qualityScores['no_' . AnalyticsDocument::INTERNAL_LINK_BROKEN_COUNT] = $brokenLinkScore;
451007225e5Sgerardnico            $ruleResults[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = self::PASSED;
452007225e5Sgerardnico        }
453007225e5Sgerardnico
454007225e5Sgerardnico        /**
455e8b2ff59SNickeau         * Media
456e8b2ff59SNickeau         */
457e8b2ff59SNickeau        $mediasStats = [
458c3437056SNickeau            "total_count" => self::getAndUnset($statExport, AnalyticsDocument::MEDIA_COUNT, 0),
459c3437056SNickeau            "internal_count" => self::getAndUnset($statExport, AnalyticsDocument::INTERNAL_MEDIA_COUNT, 0),
460c3437056SNickeau            "internal_broken_count" => self::getAndUnset($statExport, AnalyticsDocument::INTERNAL_BROKEN_MEDIA_COUNT, 0),
461c3437056SNickeau            "external_count" => self::getAndUnset($statExport, AnalyticsDocument::EXTERNAL_MEDIA_COUNT, 0)
462e8b2ff59SNickeau        ];
463e8b2ff59SNickeau        $statExport['media'] = $mediasStats;
464e8b2ff59SNickeau
465e8b2ff59SNickeau        /**
466007225e5Sgerardnico         * Changes, the more changes the better
467007225e5Sgerardnico         */
468c3437056SNickeau        $qualityScores[AnalyticsDocument::EDITS_COUNT] = $statExport[AnalyticsDocument::EDITS_COUNT] * $this->getConf(self::CONF_QUALITY_SCORE_CHANGES_FACTOR, 0.25);
469007225e5Sgerardnico
470007225e5Sgerardnico
471007225e5Sgerardnico        /**
472007225e5Sgerardnico         * Quality Score
473007225e5Sgerardnico         */
474007225e5Sgerardnico        ksort($qualityScores);
475007225e5Sgerardnico        $qualityScoring = array();
47608ca4f85Sgerardnico        $qualityScoring[self::SCORE] = array_sum($qualityScores);
477007225e5Sgerardnico        $qualityScoring["scores"] = $qualityScores;
478007225e5Sgerardnico
479007225e5Sgerardnico
480007225e5Sgerardnico        /**
481007225e5Sgerardnico         * The rule that if broken will set the quality level to low
482007225e5Sgerardnico         */
483007225e5Sgerardnico        $brokenRules = array();
484007225e5Sgerardnico        foreach ($ruleResults as $ruleName => $ruleResult) {
485007225e5Sgerardnico            if ($ruleResult == self::FAILED) {
486007225e5Sgerardnico                $brokenRules[] = $ruleName;
487007225e5Sgerardnico            }
488007225e5Sgerardnico        }
489007225e5Sgerardnico        $ruleErrorCount = sizeof($brokenRules);
490007225e5Sgerardnico        if ($ruleErrorCount > 0) {
491007225e5Sgerardnico            $qualityResult = $ruleErrorCount . " quality rules errors";
492007225e5Sgerardnico        } else {
493007225e5Sgerardnico            $qualityResult = "All quality rules passed";
494007225e5Sgerardnico        }
495007225e5Sgerardnico
496007225e5Sgerardnico        /**
497fa5961eaSgerardnico         * Low level Computation
498007225e5Sgerardnico         */
499007225e5Sgerardnico        $mandatoryRules = preg_split("/,/", $this->getConf(self::CONF_MANDATORY_QUALITY_RULES));
500007225e5Sgerardnico        $mandatoryRulesBroken = [];
501007225e5Sgerardnico        foreach ($mandatoryRules as $lowLevelRule) {
502007225e5Sgerardnico            if (in_array($lowLevelRule, $brokenRules)) {
503007225e5Sgerardnico                $mandatoryRulesBroken[] = $lowLevelRule;
504007225e5Sgerardnico            }
505007225e5Sgerardnico        }
506fa5961eaSgerardnico        /**
507c3437056SNickeau         * Low Level
508fa5961eaSgerardnico         */
509007225e5Sgerardnico        $lowLevel = false;
51085e82846SNickeau        $brokenRulesCount = sizeof($mandatoryRulesBroken);
51185e82846SNickeau        if ($brokenRulesCount > 0) {
512007225e5Sgerardnico            $lowLevel = true;
51385e82846SNickeau            $quality["message"] = "$brokenRulesCount mandatory rules broken.";
51485e82846SNickeau        } else {
51585e82846SNickeau            $quality["message"] = "No mandatory rules broken";
516007225e5Sgerardnico        }
517*4cadd4f8SNickeau        if ($this->page->isSecondarySlot()) {
518c3437056SNickeau            $lowLevel = false;
5199b9e6d1fSgerardnico        }
520c3437056SNickeau        $this->page->setLowQualityIndicatorCalculation($lowLevel);
521007225e5Sgerardnico
522007225e5Sgerardnico        /**
523007225e5Sgerardnico         * Building the quality object in order
524007225e5Sgerardnico         */
525c3437056SNickeau        $quality[AnalyticsDocument::LOW] = $lowLevel;
526007225e5Sgerardnico        if (sizeof($mandatoryRulesBroken) > 0) {
527007225e5Sgerardnico            ksort($mandatoryRulesBroken);
528c3437056SNickeau            $quality[AnalyticsDocument::FAILED_MANDATORY_RULES] = $mandatoryRulesBroken;
529007225e5Sgerardnico        }
53008ca4f85Sgerardnico        $quality[self::SCORING] = $qualityScoring;
531c3437056SNickeau        $quality[AnalyticsDocument::RULES][self::RESULT] = $qualityResult;
532007225e5Sgerardnico        if (!empty($ruleInfo)) {
533c3437056SNickeau            $quality[AnalyticsDocument::RULES]["info"] = $ruleInfo;
534007225e5Sgerardnico        }
535007225e5Sgerardnico
536007225e5Sgerardnico        ksort($ruleResults);
537c3437056SNickeau        $quality[AnalyticsDocument::RULES][AnalyticsDocument::DETAILS] = $ruleResults;
538007225e5Sgerardnico
539007225e5Sgerardnico        /**
540007225e5Sgerardnico         * Metadata
541007225e5Sgerardnico         */
542c3437056SNickeau        $page = Page::createPageFromGlobalDokuwikiId();
5431fa8c418SNickeau        $meta = $page->getMetadataForRendering();
54437748cd8SNickeau        foreach ($meta as $key => $value) {
54537748cd8SNickeau            /**
54637748cd8SNickeau             * The metadata may have been set
54737748cd8SNickeau             * by frontmatter
54837748cd8SNickeau             */
54937748cd8SNickeau            if (!isset($this->metadata[$key])) {
55037748cd8SNickeau                $this->metadata[$key] = $value;
551c42a1196Sgerardnico            }
55237748cd8SNickeau        }
553007225e5Sgerardnico
554007225e5Sgerardnico
555007225e5Sgerardnico        /**
556007225e5Sgerardnico         * Building the Top JSON in order
557007225e5Sgerardnico         */
558007225e5Sgerardnico        global $ID;
5592c067407Sgerardnico        $finalStats = array();
560c42a1196Sgerardnico        $finalStats["date"] = date('Y-m-d H:i:s', time());
56137748cd8SNickeau        ksort($this->metadata);
562c3437056SNickeau        $finalStats[AnalyticsDocument::METADATA] = $this->metadata;
563007225e5Sgerardnico        ksort($statExport);
564c3437056SNickeau        $finalStats[AnalyticsDocument::STATISTICS] = $statExport;
565c3437056SNickeau        $finalStats[AnalyticsDocument::QUALITY] = $quality; // Quality after the sort to get them at the end
566007225e5Sgerardnico
567007225e5Sgerardnico
568007225e5Sgerardnico        /**
569007225e5Sgerardnico         * The result can be seen with
570007225e5Sgerardnico         * doku.php?id=somepage&do=export_combo_analysis
5717c33ecc6Sgerardnico         *
5727c33ecc6Sgerardnico         * Set the header temporarily for the export.php file
57385e82846SNickeau         *
57485e82846SNickeau         * The mode in the export is
575007225e5Sgerardnico         */
57685e82846SNickeau        $mode = "combo_" . $this->getPluginComponent();
5777c33ecc6Sgerardnico        p_set_metadata(
5787c33ecc6Sgerardnico            $ID,
57985e82846SNickeau            array("format" => array($mode => array("Content-Type" => 'application/json'))),
5807c33ecc6Sgerardnico            false,
581c3437056SNickeau            false // Persistence is needed because there is a cache
5827c33ecc6Sgerardnico        );
5832c067407Sgerardnico        $json_encoded = json_encode($finalStats, JSON_PRETTY_PRINT);
584007225e5Sgerardnico
585007225e5Sgerardnico        $this->doc .= $json_encoded;
586007225e5Sgerardnico
587007225e5Sgerardnico    }
588007225e5Sgerardnico
589007225e5Sgerardnico    /**
590007225e5Sgerardnico     */
591007225e5Sgerardnico    public function getFormat()
592007225e5Sgerardnico    {
593531e725cSNickeau        return self::RENDERER_FORMAT;
594007225e5Sgerardnico    }
595007225e5Sgerardnico
596007225e5Sgerardnico
597007225e5Sgerardnico
598007225e5Sgerardnico    public function header($text, $level, $pos)
599007225e5Sgerardnico    {
600c3437056SNickeau        if (!array_key_exists(AnalyticsDocument::HEADING_COUNT, $this->stats)) {
601c3437056SNickeau            $this->stats[AnalyticsDocument::HEADING_COUNT] = [];
602ebdc69ceSgerardnico        }
603ebdc69ceSgerardnico        $heading = 'h' . $level;
604ebdc69ceSgerardnico        if (!array_key_exists(
605ebdc69ceSgerardnico            $heading,
606c3437056SNickeau            $this->stats[AnalyticsDocument::HEADING_COUNT])) {
607c3437056SNickeau            $this->stats[AnalyticsDocument::HEADING_COUNT][$heading] = 0;
608ebdc69ceSgerardnico        }
609c3437056SNickeau        $this->stats[AnalyticsDocument::HEADING_COUNT][$heading]++;
610ebdc69ceSgerardnico
611007225e5Sgerardnico        $this->headerId++;
612c3437056SNickeau        $this->stats[AnalyticsDocument::HEADER_POSITION][$this->headerId] = $heading;
613ebdc69ceSgerardnico
614ebdc69ceSgerardnico        /**
615ebdc69ceSgerardnico         * Store the level of each heading
616ebdc69ceSgerardnico         * They should only go from low to highest value
617ebdc69ceSgerardnico         * for a good outline
618ebdc69ceSgerardnico         */
619c3437056SNickeau        if (!array_key_exists(AnalyticsDocument::HEADING_COUNT, $this->stats)) {
620ebdc69ceSgerardnico            $this->stats[self::HEADER_STRUCT] = [];
621ebdc69ceSgerardnico        }
622ebdc69ceSgerardnico        $this->stats[self::HEADER_STRUCT][] = $level;
623007225e5Sgerardnico
624007225e5Sgerardnico    }
625007225e5Sgerardnico
626007225e5Sgerardnico    public function smiley($smiley)
627007225e5Sgerardnico    {
628007225e5Sgerardnico        if ($smiley == 'FIXME') $this->stats[self::FIXME]++;
629007225e5Sgerardnico    }
630007225e5Sgerardnico
631007225e5Sgerardnico    public function linebreak()
632007225e5Sgerardnico    {
633007225e5Sgerardnico        if (!$this->tableopen) {
634007225e5Sgerardnico            $this->stats['linebreak']++;
635007225e5Sgerardnico        }
636007225e5Sgerardnico    }
637007225e5Sgerardnico
638007225e5Sgerardnico    public function table_open($maxcols = null, $numrows = null, $pos = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
639007225e5Sgerardnico    {
640007225e5Sgerardnico        $this->tableopen = true;
641007225e5Sgerardnico    }
642007225e5Sgerardnico
643007225e5Sgerardnico    public function table_close($pos = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
644007225e5Sgerardnico    {
645007225e5Sgerardnico        $this->tableopen = false;
646007225e5Sgerardnico    }
647007225e5Sgerardnico
648007225e5Sgerardnico    public function hr()
649007225e5Sgerardnico    {
650007225e5Sgerardnico        $this->stats['hr']++;
651007225e5Sgerardnico    }
652007225e5Sgerardnico
653007225e5Sgerardnico    public function quote_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
654007225e5Sgerardnico    {
655007225e5Sgerardnico        $this->stats['quote_count']++;
656007225e5Sgerardnico        $this->quotelevel++;
657007225e5Sgerardnico        $this->stats['quote_nest'] = max($this->quotelevel, $this->stats['quote_nest']);
658007225e5Sgerardnico    }
659007225e5Sgerardnico
660007225e5Sgerardnico    public function quote_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
661007225e5Sgerardnico    {
662007225e5Sgerardnico        $this->quotelevel--;
663007225e5Sgerardnico    }
664007225e5Sgerardnico
665007225e5Sgerardnico    public function strong_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
666007225e5Sgerardnico    {
667007225e5Sgerardnico        $this->formattingBracket++;
668007225e5Sgerardnico    }
669007225e5Sgerardnico
670007225e5Sgerardnico    public function strong_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
671007225e5Sgerardnico    {
672007225e5Sgerardnico        $this->formattingBracket--;
673007225e5Sgerardnico    }
674007225e5Sgerardnico
675007225e5Sgerardnico    public function emphasis_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
676007225e5Sgerardnico    {
677007225e5Sgerardnico        $this->formattingBracket++;
678007225e5Sgerardnico    }
679007225e5Sgerardnico
680007225e5Sgerardnico    public function emphasis_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
681007225e5Sgerardnico    {
682007225e5Sgerardnico        $this->formattingBracket--;
683007225e5Sgerardnico    }
684007225e5Sgerardnico
685007225e5Sgerardnico    public function underline_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
686007225e5Sgerardnico    {
687007225e5Sgerardnico        $this->formattingBracket++;
688007225e5Sgerardnico    }
689007225e5Sgerardnico
690*4cadd4f8SNickeau    public function addToDescription($text){
691*4cadd4f8SNickeau
692*4cadd4f8SNickeau    }
693*4cadd4f8SNickeau
694007225e5Sgerardnico    public function underline_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
695007225e5Sgerardnico    {
696007225e5Sgerardnico        $this->formattingBracket--;
697007225e5Sgerardnico    }
698007225e5Sgerardnico
699007225e5Sgerardnico    public function cdata($text)
700007225e5Sgerardnico    {
701007225e5Sgerardnico
702007225e5Sgerardnico        /**
703007225e5Sgerardnico         * It seems that you receive cdata
704007225e5Sgerardnico         * when emphasis_open / underline_open / strong_open
705007225e5Sgerardnico         * Stats are not for them
706007225e5Sgerardnico         */
707007225e5Sgerardnico        if (!$this->formattingBracket) return;
708007225e5Sgerardnico
709007225e5Sgerardnico        $this->plainTextId++;
710007225e5Sgerardnico
711007225e5Sgerardnico        /**
712007225e5Sgerardnico         * Length
713007225e5Sgerardnico         */
714007225e5Sgerardnico        $len = strlen($text);
715007225e5Sgerardnico        $this->stats[self::PLAINTEXT][$this->plainTextId]['len'] = $len;
716007225e5Sgerardnico
717007225e5Sgerardnico
718007225e5Sgerardnico        /**
719007225e5Sgerardnico         * Multi-formatting
720007225e5Sgerardnico         */
721007225e5Sgerardnico        if ($this->formattingBracket > 1) {
722007225e5Sgerardnico            $numberOfFormats = 1 * ($this->formattingBracket - 1);
723007225e5Sgerardnico            $this->stats[self::PLAINTEXT][$this->plainTextId]['multiformat'] += $numberOfFormats;
724007225e5Sgerardnico        }
725007225e5Sgerardnico
726007225e5Sgerardnico        /**
727007225e5Sgerardnico         * Total
728007225e5Sgerardnico         */
729007225e5Sgerardnico        $this->stats[self::PLAINTEXT][0] += $len;
730007225e5Sgerardnico    }
731007225e5Sgerardnico
732007225e5Sgerardnico    public function internalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null)
733007225e5Sgerardnico    {
734c3437056SNickeau        $this->stats[AnalyticsDocument::INTERNAL_MEDIA_COUNT]++;
735007225e5Sgerardnico    }
736007225e5Sgerardnico
737007225e5Sgerardnico    public function externalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null)
738007225e5Sgerardnico    {
739c3437056SNickeau        $this->stats[AnalyticsDocument::EXTERNAL_MEDIA_COUNT]++;
740007225e5Sgerardnico    }
741007225e5Sgerardnico
742007225e5Sgerardnico    public function reset()
743007225e5Sgerardnico    {
744007225e5Sgerardnico        $this->stats = array();
74537748cd8SNickeau        $this->metadata = array();
746007225e5Sgerardnico        $this->headerId = 0;
747007225e5Sgerardnico    }
748007225e5Sgerardnico
749c3437056SNickeau    public function setAnalyticsMetaForReporting($key, $value)
750007225e5Sgerardnico    {
75137748cd8SNickeau        $this->metadata[$key] = $value;
752007225e5Sgerardnico    }
753007225e5Sgerardnico
754007225e5Sgerardnico
755007225e5Sgerardnico}
756007225e5Sgerardnico
757