xref: /plugin/combo/renderer/analytics.php (revision f3748b38fbeea8fd460b3c8b25641c56e84a356c)
1<?php
2
3
4use ComboStrap\Analytics;
5use ComboStrap\LinkUtility;
6use ComboStrap\LogUtility;
7use ComboStrap\LowQualityPage;
8use ComboStrap\Sqlite;
9use ComboStrap\Text;
10use ComboStrap\Page;
11use dokuwiki\ChangeLog\PageChangeLog;
12
13require_once(__DIR__ . '/../class/Text.php');
14require_once(__DIR__ . '/../class/LowQualityPage.php');
15require_once(__DIR__ . '/../class/Analytics.php');
16
17
18/**
19 * A analysis Renderer that exports stats/quality/metadata in a json format
20 * You can export the data with
21 * doku.php?id=somepage&do=export_combo_analytics
22 */
23class renderer_plugin_combo_analytics extends Doku_Renderer
24{
25    const DATE_CREATED = 'date_created';
26    const PLAINTEXT = 'formatted';
27    const RESULT = "result";
28    const DESCRIPTION = "description";
29    const PASSED = "Passed";
30    const FAILED = "Failed";
31    const FIXME = 'fixme';
32
33    /**
34     * Rules key
35     */
36    const RULE_WORDS_MINIMAL = 'words_min';
37    const RULE_OUTLINE_STRUCTURE = "outline_structure";
38    const RULE_INTERNAL_BACKLINKS_MIN = 'internal_backlinks_min';
39    const RULE_WORDS_MAXIMAL = "words_max";
40    const RULE_AVERAGE_WORDS_BY_SECTION_MIN = 'words_by_section_avg_min';
41    const RULE_AVERAGE_WORDS_BY_SECTION_MAX = 'words_by_section_avg_max';
42    const RULE_INTERNAL_LINKS_MIN = 'internal_links_min';
43    const RULE_INTERNAL_BROKEN_LINKS_MAX = 'internal_links_broken_max';
44    const RULE_DESCRIPTION_PRESENT = 'description_present';
45    const RULE_FIXME = "fixme_min";
46    const RULE_TITLE_PRESENT = "title_present";
47    const RULE_CANONICAL_PRESENT = "canonical_present";
48
49    /**
50     * The default man
51     */
52    const CONF_MANDATORY_QUALITY_RULES_DEFAULT_VALUE = [
53        self::RULE_WORDS_MINIMAL,
54        self::RULE_INTERNAL_BACKLINKS_MIN,
55        self::RULE_INTERNAL_LINKS_MIN
56    ];
57    const CONF_MANDATORY_QUALITY_RULES = "mandatoryQualityRules";
58
59    /**
60     * Quality Score factors
61     * They are used to calculate the score
62     */
63    const CONF_QUALITY_SCORE_INTERNAL_BACKLINK_FACTOR = 'qualityScoreInternalBacklinksFactor';
64    const CONF_QUALITY_SCORE_INTERNAL_LINK_FACTOR = 'qualityScoreInternalLinksFactor';
65    const CONF_QUALITY_SCORE_TITLE_PRESENT = 'qualityScoreTitlePresent';
66    const CONF_QUALITY_SCORE_CORRECT_HEADER_STRUCTURE = 'qualityScoreCorrectOutline';
67    const CONF_QUALITY_SCORE_CORRECT_CONTENT = 'qualityScoreCorrectContentLength';
68    const CONF_QUALITY_SCORE_NO_FIXME = 'qualityScoreNoFixMe';
69    const CONF_QUALITY_SCORE_CORRECT_WORD_SECTION_AVERAGE = 'qualityScoreCorrectWordSectionAvg';
70    const CONF_QUALITY_SCORE_INTERNAL_LINK_BROKEN_FACTOR = 'qualityScoreNoBrokenLinks';
71    const CONF_QUALITY_SCORE_CHANGES_FACTOR = 'qualityScoreChangesFactor';
72    const CONF_QUALITY_SCORE_DESCRIPTION_PRESENT = 'qualityScoreDescriptionPresent';
73    const CONF_QUALITY_SCORE_CANONICAL_PRESENT = 'qualityScoreCanonicalPresent';
74
75
76    /**
77     * The processing data
78     * that should be {@link  renderer_plugin_combo_analysis::reset()}
79     */
80    public $stats = array(); // the stats
81    protected $metadata = array(); // the metadata
82    protected $headerId = 0; // the id of the header on the page (first, second, ...)
83
84    /**
85     * Don't known this variable ?
86     */
87    protected $quotelevel = 0;
88    protected $formattingBracket = 0;
89    protected $tableopen = false;
90    private $plainTextId = 0;
91    /**
92     * @var Page
93     */
94    private $page;
95
96    public function document_start()
97    {
98        global $ID;
99        $this->page = new Page($ID);
100        $analytics = $this->page->getAnalyticsFromDb();
101        if (!empty($analytics)) {
102            $this->internalLinkBefore = $analytics[Analytics::STATISTICS];
103        }
104
105    }
106
107
108    /**
109     * Here the score is calculated
110     */
111    public function document_end() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
112    {
113        /**
114         * The exported object
115         */
116        $statExport = $this->stats;
117
118        /**
119         * The metadata
120         */
121        global $ID;
122        $meta = p_get_metadata($ID);
123
124        /**
125         * Edit author stats
126         */
127        $changelog = new PageChangeLog($ID);
128        $revs = $changelog->getRevisions(0, 10000);
129        array_push($revs, $meta['last_change']['date']);
130        $statExport[Analytics::EDITS_COUNT] = count($revs);
131        foreach ($revs as $rev) {
132            $info = $changelog->getRevisionInfo($rev);
133            if ($info['user']) {
134                $statExport['authors'][$info['user']] += 1;
135            } else {
136                $statExport['authors']['*'] += 1;
137            }
138        }
139
140        /**
141         * Word and chars count
142         * The word count does not take into account
143         * words with non-words characters such as < =
144         * Therefore the node and attribute are not taken in the count
145         */
146        $text = rawWiki($ID);
147        $statExport[Analytics::CHARS_COUNT] = strlen($text);
148        $statExport[Analytics::WORDS_COUNT] = Text::getWordCount($text);
149
150
151        /**
152         * Internal link distance summary calculation
153         */
154        if (array_key_exists(Analytics::INTERNAL_LINK_DISTANCE, $statExport)) {
155            $linkLengths = $statExport[Analytics::INTERNAL_LINK_DISTANCE];
156            unset($statExport[Analytics::INTERNAL_LINK_DISTANCE]);
157            $countBacklinks = count($linkLengths);
158            $statExport[Analytics::INTERNAL_LINK_DISTANCE]['avg'] = null;
159            $statExport[Analytics::INTERNAL_LINK_DISTANCE]['max'] = null;
160            $statExport[Analytics::INTERNAL_LINK_DISTANCE]['min'] = null;
161            if ($countBacklinks > 0) {
162                $statExport[Analytics::INTERNAL_LINK_DISTANCE]['avg'] = array_sum($linkLengths) / $countBacklinks;
163                $statExport[Analytics::INTERNAL_LINK_DISTANCE]['max'] = max($linkLengths);
164                $statExport[Analytics::INTERNAL_LINK_DISTANCE]['min'] = min($linkLengths);
165            }
166        }
167
168        /**
169         * Quality Report / Rules
170         */
171        // The array that hold the results of the quality rules
172        $ruleResults = array();
173        // The array that hold the quality score details
174        $qualityScores = array();
175
176
177        /**
178         * No fixme
179         */
180        $fixmeCount = $this->stats[self::FIXME];
181        $statExport[self::FIXME] = $fixmeCount == null ? 0 : $fixmeCount;
182        if ($fixmeCount != 0) {
183            $ruleResults[self::RULE_FIXME] = self::FAILED;
184            $qualityScores['no_' . self::FIXME] = 0;
185        } else {
186            $ruleResults[self::RULE_FIXME] = self::PASSED;
187            $qualityScores['no_' . self::FIXME] = $this->getConf(self::CONF_QUALITY_SCORE_NO_FIXME, 1);;
188        }
189
190        /**
191         * A title should be present
192         */
193        if (empty($this->metadata[Analytics::TITLE])) {
194            $ruleResults[self::RULE_TITLE_PRESENT] = self::FAILED;
195            $ruleInfo[self::RULE_TITLE_PRESENT] = "A title is not present in the frontmatter";
196            $this->metadata[Analytics::TITLE] = $meta[Analytics::TITLE];
197            $qualityScores[self::RULE_TITLE_PRESENT] = 0;
198        } else {
199            $qualityScores[self::RULE_TITLE_PRESENT] = $this->getConf(self::CONF_QUALITY_SCORE_TITLE_PRESENT, 10);;
200            $ruleResults[self::RULE_TITLE_PRESENT] = self::PASSED;
201        }
202
203        /**
204         * A description should be present
205         */
206        if (empty($this->metadata[self::DESCRIPTION])) {
207            $ruleResults[self::RULE_DESCRIPTION_PRESENT] = self::FAILED;
208            $ruleInfo[self::RULE_DESCRIPTION_PRESENT] = "A description is not present in the frontmatter";
209            $this->metadata[self::DESCRIPTION] = $meta[self::DESCRIPTION]["abstract"];
210            $qualityScores[self::RULE_DESCRIPTION_PRESENT] = 0;
211        } else {
212            $qualityScores[self::RULE_DESCRIPTION_PRESENT] = $this->getConf(self::CONF_QUALITY_SCORE_DESCRIPTION_PRESENT, 8);;
213            $ruleResults[self::RULE_DESCRIPTION_PRESENT] = self::PASSED;
214        }
215
216        /**
217         * A canonical should be present
218         */
219        if (empty($this->metadata[Page::CANONICAL_PROPERTY])) {
220            global $conf;
221            $root = $conf['start'];
222            if ($ID!=$root) {
223                $qualityScores[self::RULE_CANONICAL_PRESENT] = 0;
224                $ruleResults[self::RULE_CANONICAL_PRESENT] = self::FAILED;
225                $ruleInfo[self::RULE_CANONICAL_PRESENT] = "A canonical is not present in the frontmatter";
226            }
227        } else {
228            $qualityScores[self::RULE_CANONICAL_PRESENT] = $this->getConf(self::CONF_QUALITY_SCORE_CANONICAL_PRESENT, 5);;
229            $ruleResults[self::RULE_CANONICAL_PRESENT] = self::PASSED;
230        }
231
232        /**
233         * Outline / Header structure
234         */
235        $treeError = 0;
236        $headersCount = 0;
237        if (array_key_exists(Analytics::HEADER_POSITION, $this->stats)) {
238            $headersCount = count($this->stats[Analytics::HEADER_POSITION]);
239            unset($statExport[Analytics::HEADER_POSITION]);
240            for ($i = 1; $i < $headersCount; $i++) {
241                $currentHeaderLevel = $this->stats['header_struct'][$i];
242                $previousHeaderLevel = $this->stats['header_struct'][$i - 1];
243                if ($currentHeaderLevel - $previousHeaderLevel > 1) {
244                    $treeError += 1;
245                    $ruleInfo[self::RULE_OUTLINE_STRUCTURE] = "The " . $i . " header (h" . $currentHeaderLevel . ") has a level bigger than its precedent (" . $previousHeaderLevel . ")";
246                }
247            }
248        }
249        if ($treeError > 0 || $headersCount == 0) {
250            $qualityScores['correct_outline'] = 0;
251            $ruleResults[self::RULE_OUTLINE_STRUCTURE] = self::FAILED;
252            if ($headersCount == 0) {
253                $ruleInfo[self::RULE_OUTLINE_STRUCTURE] = "There is no header";
254            }
255        } else {
256            $qualityScores['correct_outline'] = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_HEADER_STRUCTURE, 3);
257            $ruleResults[self::RULE_OUTLINE_STRUCTURE] = self::PASSED;
258        }
259
260
261        /**
262         * Document length
263         */
264        $minimalWordCount = 50;
265        $maximalWordCount = 1500;
266        $correctContentLength = true;
267        if ($statExport[Analytics::WORDS_COUNT] < $minimalWordCount) {
268            $ruleResults[self::RULE_WORDS_MINIMAL] = self::FAILED;
269            $correctContentLength = false;
270            $ruleInfo[self::RULE_WORDS_MINIMAL] = "The number of words is less than {$minimalWordCount}";
271        } else {
272            $ruleResults[self::RULE_WORDS_MINIMAL] = self::PASSED;
273        }
274        if ($statExport[Analytics::WORDS_COUNT] > $maximalWordCount) {
275            $ruleResults[self::RULE_WORDS_MAXIMAL] = self::FAILED;
276            $ruleInfo[self::RULE_WORDS_MAXIMAL] = "The number of words is more than {$maximalWordCount}";
277            $correctContentLength = false;
278        } else {
279            $ruleResults[self::RULE_WORDS_MAXIMAL] = self::PASSED;
280        }
281        if ($correctContentLength) {
282            $qualityScores['correct_content_length'] = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_CONTENT, 10);
283        } else {
284            $qualityScores['correct_content_length'] = 0;
285        }
286
287
288        /**
289         * Average Number of words by header section to text ratio
290         */
291        $headers = $this->stats[Analytics::HEADERS_COUNT];
292        if ($headers != null) {
293            $headerCount = array_sum($headers);
294            $headerCount--; // h1 is supposed to have no words
295            if ($headerCount > 0) {
296
297                $avgWordsCountBySection = round($this->stats[Analytics::WORDS_COUNT] / $headerCount);
298                $statExport['word_section_count']['avg'] = $avgWordsCountBySection;
299
300                /**
301                 * Min words by header section
302                 */
303                $wordsByHeaderMin = 20;
304                /**
305                 * Max words by header section
306                 */
307                $wordsByHeaderMax = 300;
308                $correctAverageWordsBySection = true;
309                if ($avgWordsCountBySection < $wordsByHeaderMin) {
310                    $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = self::FAILED;
311                    $correctAverageWordsBySection = false;
312                    $ruleInfo[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = "The number of words by section is less than {$wordsByHeaderMin}";
313                } else {
314                    $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = self::PASSED;
315                }
316                if ($avgWordsCountBySection > $wordsByHeaderMax) {
317                    $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = self::FAILED;
318                    $correctAverageWordsBySection = false;
319                    $ruleInfo[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = "The number of words by section is more than {$wordsByHeaderMax}";
320                } else {
321                    $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = self::PASSED;
322                }
323                if ($correctAverageWordsBySection) {
324                    $qualityScores['correct_word_avg_by_section'] = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_WORD_SECTION_AVERAGE, 10);
325                } else {
326                    $qualityScores['correct_word_avg_by_section'] = 0;
327                }
328
329            }
330        }
331
332        /**
333         * Internal Backlinks rule
334         *
335         * If a page is a low quality page, if the process run
336         * anonymous, we will not see all {@link ft_backlinks()}
337         * we use then the index directly to avoid confusion
338         */
339        $backlinks = idx_get_indexer()->lookupKey('relation_references', $ID);
340        $countBacklinks = count($backlinks);
341        $statExport[Analytics::INTERNAL_BACKLINKS_COUNT] = $countBacklinks;
342        if ($countBacklinks == 0) {
343            $qualityScores[Analytics::INTERNAL_BACKLINKS_COUNT] = 0;
344            $ruleResults[self::RULE_INTERNAL_BACKLINKS_MIN] = self::FAILED;
345            $ruleInfo[self::RULE_INTERNAL_BACKLINKS_MIN] = "There is no backlinks";
346        } else {
347            $qualityScores[Analytics::INTERNAL_BACKLINKS_COUNT] = $countBacklinks * $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_BACKLINK_FACTOR, 1);
348            $ruleResults[self::RULE_INTERNAL_BACKLINKS_MIN] = self::PASSED;
349        }
350
351        /**
352         * Internal links
353         */
354        $internalLinksCount = $this->stats[Analytics::INTERNAL_LINKS_COUNT];
355        if ($internalLinksCount == 0) {
356            $qualityScores[Analytics::INTERNAL_LINKS_COUNT] = 0;
357            $ruleResults[self::RULE_INTERNAL_LINKS_MIN] = self::FAILED;
358            $ruleInfo[self::RULE_INTERNAL_LINKS_MIN] = "There is no internal links";
359        } else {
360            $ruleResults[self::RULE_INTERNAL_LINKS_MIN] = self::PASSED;
361            $qualityScores[Analytics::INTERNAL_LINKS_COUNT] = $countBacklinks * $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_LINK_FACTOR, 1);;
362        }
363
364        /**
365         * Broken Links
366         */
367        $brokenLinksCount = $this->stats[Analytics::INTERNAL_LINKS_BROKEN_COUNT];
368        if ($brokenLinksCount > 2) {
369            $qualityScores['no_' . Analytics::INTERNAL_LINKS_BROKEN_COUNT] = 0;
370            $ruleResults[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = self::FAILED;
371            $ruleInfo[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = "There is {$brokenLinksCount} broken links";
372        } else {
373            $qualityScores['no_' . Analytics::INTERNAL_LINKS_BROKEN_COUNT] = $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_LINK_BROKEN_FACTOR, 2);;;
374            $ruleResults[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = self::PASSED;
375        }
376
377        /**
378         * Changes, the more changes the better
379         */
380        $qualityScores[Analytics::EDITS_COUNT] = $this->stats[Analytics::EDITS_COUNT] * $this->getConf(self::CONF_QUALITY_SCORE_CHANGES_FACTOR, 0.25);;;
381
382
383
384
385        /**
386         * Quality Score
387         */
388        ksort($qualityScores);
389        $qualityScoring = array();
390        $qualityScoring["score"] = array_sum($qualityScores);
391        $qualityScoring["scores"] = $qualityScores;
392
393
394        /**
395         * The rule that if broken will set the quality level to low
396         */
397        $brokenRules = array();
398        foreach ($ruleResults as $ruleName => $ruleResult) {
399            if ($ruleResult == self::FAILED) {
400                $brokenRules[] = $ruleName;
401            }
402        }
403        $ruleErrorCount = sizeof($brokenRules);
404        if ($ruleErrorCount > 0) {
405            $qualityResult = $ruleErrorCount . " quality rules errors";
406        } else {
407            $qualityResult = "All quality rules passed";
408        }
409
410        /**
411         * Low level
412         */
413        $mandatoryRules = preg_split("/,/", $this->getConf(self::CONF_MANDATORY_QUALITY_RULES));
414        $mandatoryRulesBroken = [];
415        foreach ($mandatoryRules as $lowLevelRule) {
416            if (in_array($lowLevelRule, $brokenRules)) {
417                $mandatoryRulesBroken[] = $lowLevelRule;
418            }
419        }
420        $lowLevel = false;
421        if (sizeof($mandatoryRulesBroken) > 0) {
422            $lowLevel = true;
423        }
424        LowQualityPage::setLowQualityPage($ID, $lowLevel);
425
426        /**
427         * Building the quality object in order
428         */
429        $quality[Analytics::LOW] = $lowLevel;
430        if (sizeof($mandatoryRulesBroken) > 0) {
431            ksort($mandatoryRulesBroken);
432            $quality['failed_mandatory_rules'] = $mandatoryRulesBroken;
433        }
434        $quality["scoring"] = $qualityScoring;
435        $quality[Analytics::RULES][self::RESULT] = $qualityResult;
436        if (!empty($ruleInfo)) {
437            $quality[Analytics::RULES]["info"] = $ruleInfo;
438        }
439
440        ksort($ruleResults);
441        $quality[Analytics::RULES][Analytics::DETAILS] = $ruleResults;
442
443        /**
444         * Metadata
445         */
446        $title = $meta['title'];
447        $this->metadata[Analytics::TITLE] = $title;
448        if ($title != $meta['h1']) {
449            $this->metadata[Analytics::H1] = $meta['h1'];
450        }
451        $timestampCreation = $meta['date']['created'];
452        $this->metadata[self::DATE_CREATED] = date('Y-m-d h:i:s', $timestampCreation);
453        $timestampModification = $meta['date']['modified'];
454        $this->metadata[Analytics::DATE_MODIFIED] = date('Y-m-d h:i:s', $timestampModification);
455        $this->metadata['age_creation'] = round((time() - $timestampCreation) / 60 / 60 / 24);
456        $this->metadata['age_modification'] = round((time() - $timestampModification) / 60 / 60 / 24);
457
458
459
460
461        /**
462         * Building the Top JSON in order
463         */
464        global $ID;
465        $finalStats = array();
466        $finalStats["id"] = $ID;
467        $finalStats["date"] = date('Y-m-d H:i:s', time());
468        $finalStats['metadata'] = $this->metadata;
469        ksort($statExport);
470        $finalStats[Analytics::STATISTICS] = $statExport;
471        $finalStats[Analytics::QUALITY] = $quality; // Quality after the sort to get them at the end
472
473
474        /**
475         * The result can be seen with
476         * doku.php?id=somepage&do=export_combo_analysis
477         */
478        /**
479         * Set the header for the export.php file
480         */
481        p_set_metadata($ID, array("format" =>
482            array("combo_" . $this->getPluginComponent() => array("Content-Type" => 'application/json'))
483        ));
484        $json_encoded = json_encode($finalStats, JSON_PRETTY_PRINT);
485
486        $page = new Page($ID);
487        $page->saveAnalytics($finalStats);
488        $this->doc .= $json_encoded;
489
490    }
491
492    /**
493     */
494    public function getFormat()
495    {
496        return Analytics::RENDERER_FORMAT;
497    }
498
499    public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content')
500    {
501
502        $attribute = array(
503            LinkUtility::ATTRIBUTE_ID => $id,
504            LinkUtility::ATTRIBUTE_TYPE => LinkUtility::TYPE_INTERNAL
505        );
506        LinkUtility::processLinkStats($attribute, $this->stats);
507
508    }
509
510    public function externallink($url, $name = null)
511    {
512        $attribute = array(
513            LinkUtility::ATTRIBUTE_ID => $url,
514            LinkUtility::ATTRIBUTE_TYPE => LinkUtility::TYPE_EXTERNAL,
515            LinkUtility::ATTRIBUTE_TITLE => $name
516        );
517        LinkUtility::processLinkStats($attribute, $this->stats);
518    }
519
520    public function header($text, $level, $pos)
521    {
522        $this->stats[Analytics::HEADERS_COUNT]['h' . $level]++;
523        $this->headerId++;
524        $this->stats[Analytics::HEADER_POSITION][$this->headerId] = 'h' . $level;
525
526    }
527
528    public function smiley($smiley)
529    {
530        if ($smiley == 'FIXME') $this->stats[self::FIXME]++;
531    }
532
533    public function linebreak()
534    {
535        if (!$this->tableopen) {
536            $this->stats['linebreak']++;
537        }
538    }
539
540    public function table_open($maxcols = null, $numrows = null, $pos = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
541    {
542        $this->tableopen = true;
543    }
544
545    public function table_close($pos = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
546    {
547        $this->tableopen = false;
548    }
549
550    public function hr()
551    {
552        $this->stats['hr']++;
553    }
554
555    public function quote_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
556    {
557        $this->stats['quote_count']++;
558        $this->quotelevel++;
559        $this->stats['quote_nest'] = max($this->quotelevel, $this->stats['quote_nest']);
560    }
561
562    public function quote_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
563    {
564        $this->quotelevel--;
565    }
566
567    public function strong_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
568    {
569        $this->formattingBracket++;
570    }
571
572    public function strong_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
573    {
574        $this->formattingBracket--;
575    }
576
577    public function emphasis_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
578    {
579        $this->formattingBracket++;
580    }
581
582    public function emphasis_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
583    {
584        $this->formattingBracket--;
585    }
586
587    public function underline_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
588    {
589        $this->formattingBracket++;
590    }
591
592    public function underline_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
593    {
594        $this->formattingBracket--;
595    }
596
597    public function cdata($text)
598    {
599
600        /**
601         * It seems that you receive cdata
602         * when emphasis_open / underline_open / strong_open
603         * Stats are not for them
604         */
605        if (!$this->formattingBracket) return;
606
607        $this->plainTextId++;
608
609        /**
610         * Length
611         */
612        $len = strlen($text);
613        $this->stats[self::PLAINTEXT][$this->plainTextId]['len'] = $len;
614
615
616        /**
617         * Multi-formatting
618         */
619        if ($this->formattingBracket > 1) {
620            $numberOfFormats = 1 * ($this->formattingBracket - 1);
621            $this->stats[self::PLAINTEXT][$this->plainTextId]['multiformat'] += $numberOfFormats;
622        }
623
624        /**
625         * Total
626         */
627        $this->stats[self::PLAINTEXT][0] += $len;
628    }
629
630    public function internalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null)
631    {
632        $this->stats[Analytics::INTERNAL_MEDIAS_COUNT]++;
633    }
634
635    public function externalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null)
636    {
637        $this->stats[Analytics::EXTERNAL_MEDIAS]++;
638    }
639
640    public function reset()
641    {
642        $this->stats = array();
643        $this->metadata = array();
644        $this->headerId = 0;
645    }
646
647    public function setMeta($key, $value)
648    {
649        $this->metadata[$key] = $value;
650    }
651
652
653}
654
655