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