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