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