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