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 if ($treeError > 0 || $headersCount == 0) { 265 $qualityScores['correct_outline'] = 0; 266 $ruleResults[self::RULE_OUTLINE_STRUCTURE] = self::FAILED; 267 if ($headersCount == 0) { 268 $ruleInfo[self::RULE_OUTLINE_STRUCTURE] = "There is no header"; 269 } 270 } else { 271 $qualityScores['correct_outline'] = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_HEADER_STRUCTURE, 3); 272 $ruleResults[self::RULE_OUTLINE_STRUCTURE] = self::PASSED; 273 } 274 275 276 /** 277 * Document length 278 */ 279 $minimalWordCount = 50; 280 $maximalWordCount = 1500; 281 $correctContentLength = true; 282 $correctLengthScore = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_CONTENT, 10); 283 $missingWords = $minimalWordCount - $statExport[Analytics::WORDS_COUNT]; 284 if ($missingWords > 0) { 285 $ruleResults[self::RULE_WORDS_MINIMAL] = self::FAILED; 286 $correctContentLength = false; 287 $ruleInfo[self::RULE_WORDS_MINIMAL] = "Add {$missingWords} words to get {$correctLengthScore} points"; 288 } else { 289 $ruleResults[self::RULE_WORDS_MINIMAL] = self::PASSED; 290 } 291 $tooMuchWords = $statExport[Analytics::WORDS_COUNT] - $maximalWordCount; 292 if ($tooMuchWords > 0) { 293 $ruleResults[self::RULE_WORDS_MAXIMAL] = self::FAILED; 294 $ruleInfo[self::RULE_WORDS_MAXIMAL] = "Delete {$tooMuchWords} words to get {$correctLengthScore} points"; 295 $correctContentLength = false; 296 } else { 297 $ruleResults[self::RULE_WORDS_MAXIMAL] = self::PASSED; 298 } 299 if ($correctContentLength) { 300 $qualityScores['correct_content_length'] = $correctLengthScore; 301 } else { 302 $qualityScores['correct_content_length'] = 0; 303 } 304 305 306 /** 307 * Average Number of words by header section to text ratio 308 */ 309 $headers = $this->stats[Analytics::HEADERS_COUNT]; 310 if ($headers != null) { 311 $headerCount = array_sum($headers); 312 $headerCount--; // h1 is supposed to have no words 313 if ($headerCount > 0) { 314 315 $avgWordsCountBySection = round($this->stats[Analytics::WORDS_COUNT] / $headerCount); 316 $statExport['word_section_count']['avg'] = $avgWordsCountBySection; 317 318 /** 319 * Min words by header section 320 */ 321 $wordsByHeaderMin = 20; 322 /** 323 * Max words by header section 324 */ 325 $wordsByHeaderMax = 300; 326 $correctAverageWordsBySection = true; 327 if ($avgWordsCountBySection < $wordsByHeaderMin) { 328 $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = self::FAILED; 329 $correctAverageWordsBySection = false; 330 $ruleInfo[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = "The number of words by section is less than {$wordsByHeaderMin}"; 331 } else { 332 $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = self::PASSED; 333 } 334 if ($avgWordsCountBySection > $wordsByHeaderMax) { 335 $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = self::FAILED; 336 $correctAverageWordsBySection = false; 337 $ruleInfo[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = "The number of words by section is more than {$wordsByHeaderMax}"; 338 } else { 339 $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = self::PASSED; 340 } 341 if ($correctAverageWordsBySection) { 342 $qualityScores['correct_word_avg_by_section'] = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_WORD_SECTION_AVERAGE, 10); 343 } else { 344 $qualityScores['correct_word_avg_by_section'] = 0; 345 } 346 347 } 348 } 349 350 /** 351 * Internal Backlinks rule 352 * 353 * If a page is a low quality page, if the process run 354 * anonymous, we will not see all {@link ft_backlinks()} 355 * we use then the index directly to avoid confusion 356 */ 357 $backlinks = idx_get_indexer()->lookupKey('relation_references', $ID); 358 $countBacklinks = count($backlinks); 359 $statExport[Analytics::INTERNAL_BACKLINKS_COUNT] = $countBacklinks; 360 $backlinkScore = $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_BACKLINK_FACTOR, 1); 361 if ($countBacklinks == 0) { 362 $qualityScores[Analytics::INTERNAL_BACKLINKS_COUNT] = 0; 363 $ruleResults[self::RULE_INTERNAL_BACKLINKS_MIN] = self::FAILED; 364 $ruleInfo[self::RULE_INTERNAL_BACKLINKS_MIN] = "Add backlinks for {$backlinkScore} point each"; 365 } else { 366 367 $qualityScores[Analytics::INTERNAL_BACKLINKS_COUNT] = $countBacklinks * $backlinkScore; 368 $ruleResults[self::RULE_INTERNAL_BACKLINKS_MIN] = self::PASSED; 369 } 370 371 /** 372 * Internal links 373 */ 374 $internalLinksCount = $this->stats[Analytics::INTERNAL_LINKS_COUNT]; 375 $internalLinkScore = $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_LINK_FACTOR, 1); 376 if ($internalLinksCount == 0) { 377 $qualityScores[Analytics::INTERNAL_LINKS_COUNT] = 0; 378 $ruleResults[self::RULE_INTERNAL_LINKS_MIN] = self::FAILED; 379 $ruleInfo[self::RULE_INTERNAL_LINKS_MIN] = "Add internal links for {$internalLinkScore} point each"; 380 } else { 381 $ruleResults[self::RULE_INTERNAL_LINKS_MIN] = self::PASSED; 382 $qualityScores[Analytics::INTERNAL_LINKS_COUNT] = $countBacklinks * $internalLinkScore; 383 } 384 385 /** 386 * Broken Links 387 */ 388 $brokenLinkScore = $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_LINK_BROKEN_FACTOR, 2); 389 $brokenLinksCount = $this->stats[Analytics::INTERNAL_LINKS_BROKEN_COUNT]; 390 if ($brokenLinksCount > 2) { 391 $qualityScores['no_' . Analytics::INTERNAL_LINKS_BROKEN_COUNT] = 0; 392 $ruleResults[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = self::FAILED; 393 $ruleInfo[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = "Delete the {$brokenLinksCount} broken links and add {$brokenLinkScore} points"; 394 } else { 395 $qualityScores['no_' . Analytics::INTERNAL_LINKS_BROKEN_COUNT] = $brokenLinkScore; 396 $ruleResults[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = self::PASSED; 397 } 398 399 /** 400 * Changes, the more changes the better 401 */ 402 $qualityScores[Analytics::EDITS_COUNT] = $this->stats[Analytics::EDITS_COUNT] * $this->getConf(self::CONF_QUALITY_SCORE_CHANGES_FACTOR, 0.25); 403 404 405 /** 406 * Quality Score 407 */ 408 ksort($qualityScores); 409 $qualityScoring = array(); 410 $qualityScoring[self::SCORE] = array_sum($qualityScores); 411 $qualityScoring["scores"] = $qualityScores; 412 413 414 /** 415 * The rule that if broken will set the quality level to low 416 */ 417 $brokenRules = array(); 418 foreach ($ruleResults as $ruleName => $ruleResult) { 419 if ($ruleResult == self::FAILED) { 420 $brokenRules[] = $ruleName; 421 } 422 } 423 $ruleErrorCount = sizeof($brokenRules); 424 if ($ruleErrorCount > 0) { 425 $qualityResult = $ruleErrorCount . " quality rules errors"; 426 } else { 427 $qualityResult = "All quality rules passed"; 428 } 429 430 /** 431 * Low level 432 * If not set manually 433 */ 434 $mandatoryRules = preg_split("/,/", $this->getConf(self::CONF_MANDATORY_QUALITY_RULES)); 435 $mandatoryRulesBroken = []; 436 foreach ($mandatoryRules as $lowLevelRule) { 437 if (in_array($lowLevelRule, $brokenRules)) { 438 $mandatoryRulesBroken[] = $lowLevelRule; 439 } 440 } 441 if (empty($this->metadata[Page::LOW_QUALITY_PAGE_INDICATOR])) { 442 $lowLevel = false; 443 if (sizeof($mandatoryRulesBroken) > 0) { 444 $lowLevel = true; 445 } 446 $this->page->setLowQualityIndicator($lowLevel); 447 } else { 448 $lowLevel = $this->metadata[Page::LOW_QUALITY_PAGE_INDICATOR]; 449 } 450 451 /** 452 * Building the quality object in order 453 */ 454 $quality[Analytics::LOW] = $lowLevel; 455 if (sizeof($mandatoryRulesBroken) > 0) { 456 ksort($mandatoryRulesBroken); 457 $quality[Analytics::FAILED_MANDATORY_RULES] = $mandatoryRulesBroken; 458 } 459 $quality[self::SCORING] = $qualityScoring; 460 $quality[Analytics::RULES][self::RESULT] = $qualityResult; 461 if (!empty($ruleInfo)) { 462 $quality[Analytics::RULES]["info"] = $ruleInfo; 463 } 464 465 ksort($ruleResults); 466 $quality[Analytics::RULES][Analytics::DETAILS] = $ruleResults; 467 468 /** 469 * Metadata 470 */ 471 $title = $meta['title']; 472 $this->metadata[Analytics::TITLE] = $title; 473 if ($title != $meta['h1']) { 474 $this->metadata[Analytics::H1] = $meta['h1']; 475 } 476 $timestampCreation = $meta['date']['created']; 477 $this->metadata[self::DATE_CREATED] = date('Y-m-d h:i:s', $timestampCreation); 478 $timestampModification = $meta['date']['modified']; 479 $this->metadata[Analytics::DATE_MODIFIED] = date('Y-m-d h:i:s', $timestampModification); 480 $this->metadata['age_creation'] = round((time() - $timestampCreation) / 60 / 60 / 24); 481 $this->metadata['age_modification'] = round((time() - $timestampModification) / 60 / 60 / 24); 482 483 484 /** 485 * Building the Top JSON in order 486 */ 487 global $ID; 488 $finalStats = array(); 489 $finalStats["id"] = $ID; 490 $finalStats["date"] = date('Y-m-d H:i:s', time()); 491 $finalStats['metadata'] = $this->metadata; 492 ksort($statExport); 493 $finalStats[Analytics::STATISTICS] = $statExport; 494 $finalStats[Analytics::QUALITY] = $quality; // Quality after the sort to get them at the end 495 496 497 /** 498 * The result can be seen with 499 * doku.php?id=somepage&do=export_combo_analysis 500 * 501 * Set the header temporarily for the export.php file 502 */ 503 p_set_metadata( 504 $ID, 505 array("format" => array("combo_" . $this->getPluginComponent() => array("Content-Type" => 'application/json'))), 506 false, 507 false // Persistence is not needed, this is just in case this is an export 508 ); 509 $json_encoded = json_encode($finalStats, JSON_PRETTY_PRINT); 510 511 $this->page->saveAnalytics($finalStats); 512 $this->doc .= $json_encoded; 513 514 } 515 516 /** 517 */ 518 public function getFormat() 519 { 520 return Analytics::RENDERER_FORMAT; 521 } 522 523 public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') 524 { 525 526 $link = new LinkUtility($id); 527 $link->setType(LinkUtility::TYPE_INTERNAL); 528 $link->processLinkStats($this->stats); 529 530 } 531 532 public function externallink($url, $name = null) 533 { 534 $link = new LinkUtility($url); 535 $link->setType(LinkUtility::TYPE_EXTERNAL); 536 if($name !=null) { 537 $link->setName($name); 538 } 539 $link->processLinkStats( $this->stats); 540 } 541 542 public function header($text, $level, $pos) 543 { 544 $this->stats[Analytics::HEADERS_COUNT]['h' . $level]++; 545 $this->headerId++; 546 $this->stats[Analytics::HEADER_POSITION][$this->headerId] = 'h' . $level; 547 548 } 549 550 public function smiley($smiley) 551 { 552 if ($smiley == 'FIXME') $this->stats[self::FIXME]++; 553 } 554 555 public function linebreak() 556 { 557 if (!$this->tableopen) { 558 $this->stats['linebreak']++; 559 } 560 } 561 562 public function table_open($maxcols = null, $numrows = null, $pos = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 563 { 564 $this->tableopen = true; 565 } 566 567 public function table_close($pos = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 568 { 569 $this->tableopen = false; 570 } 571 572 public function hr() 573 { 574 $this->stats['hr']++; 575 } 576 577 public function quote_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 578 { 579 $this->stats['quote_count']++; 580 $this->quotelevel++; 581 $this->stats['quote_nest'] = max($this->quotelevel, $this->stats['quote_nest']); 582 } 583 584 public function quote_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 585 { 586 $this->quotelevel--; 587 } 588 589 public function strong_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 590 { 591 $this->formattingBracket++; 592 } 593 594 public function strong_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 595 { 596 $this->formattingBracket--; 597 } 598 599 public function emphasis_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 600 { 601 $this->formattingBracket++; 602 } 603 604 public function emphasis_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 605 { 606 $this->formattingBracket--; 607 } 608 609 public function underline_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 610 { 611 $this->formattingBracket++; 612 } 613 614 public function underline_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 615 { 616 $this->formattingBracket--; 617 } 618 619 public function cdata($text) 620 { 621 622 /** 623 * It seems that you receive cdata 624 * when emphasis_open / underline_open / strong_open 625 * Stats are not for them 626 */ 627 if (!$this->formattingBracket) return; 628 629 $this->plainTextId++; 630 631 /** 632 * Length 633 */ 634 $len = strlen($text); 635 $this->stats[self::PLAINTEXT][$this->plainTextId]['len'] = $len; 636 637 638 /** 639 * Multi-formatting 640 */ 641 if ($this->formattingBracket > 1) { 642 $numberOfFormats = 1 * ($this->formattingBracket - 1); 643 $this->stats[self::PLAINTEXT][$this->plainTextId]['multiformat'] += $numberOfFormats; 644 } 645 646 /** 647 * Total 648 */ 649 $this->stats[self::PLAINTEXT][0] += $len; 650 } 651 652 public function internalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null) 653 { 654 $this->stats[Analytics::INTERNAL_MEDIAS_COUNT]++; 655 } 656 657 public function externalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null) 658 { 659 $this->stats[Analytics::EXTERNAL_MEDIAS]++; 660 } 661 662 public function reset() 663 { 664 $this->stats = array(); 665 $this->metadata = array(); 666 $this->headerId = 0; 667 } 668 669 public function setMeta($key, $value) 670 { 671 $this->metadata[$key] = $value; 672 } 673 674 675} 676 677