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 const HEADER_STRUCT = 'header_struct'; 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 $analyticsMetadata = 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 $dokuWikiMetadata = 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, $dokuWikiMetadata['last_change']['date']); 142 $statExport[Analytics::EDITS_COUNT] = count($revs); 143 foreach ($revs as $rev) { 144 145 146 /** 147 * Init the authors array 148 */ 149 if (!array_key_exists('authors', $statExport)) { 150 $statExport['authors'] = []; 151 } 152 /** 153 * Analytics by users 154 */ 155 $info = $changelog->getRevisionInfo($rev); 156 if (is_array($info)) { 157 $user = "*"; 158 if (array_key_exists('user', $info)) { 159 $user = $info['user']; 160 } 161 if (!array_key_exists('authors', $statExport['authors'])) { 162 $statExport['authors'][$user] = 0; 163 } 164 $statExport['authors'][$user] += 1; 165 } 166 } 167 168 /** 169 * Word and chars count 170 * The word count does not take into account 171 * words with non-words characters such as < = 172 * Therefore the node and attribute are not taken in the count 173 */ 174 $text = rawWiki($ID); 175 $statExport[Analytics::CHARS_COUNT] = strlen($text); 176 $statExport[Analytics::WORDS_COUNT] = StringUtility::getWordCount($text); 177 178 179 /** 180 * Internal link distance summary calculation 181 */ 182 if (array_key_exists(Analytics::INTERNAL_LINK_DISTANCE, $statExport)) { 183 $linkLengths = $statExport[Analytics::INTERNAL_LINK_DISTANCE]; 184 unset($statExport[Analytics::INTERNAL_LINK_DISTANCE]); 185 $countBacklinks = count($linkLengths); 186 $statExport[Analytics::INTERNAL_LINK_DISTANCE]['avg'] = null; 187 $statExport[Analytics::INTERNAL_LINK_DISTANCE]['max'] = null; 188 $statExport[Analytics::INTERNAL_LINK_DISTANCE]['min'] = null; 189 if ($countBacklinks > 0) { 190 $statExport[Analytics::INTERNAL_LINK_DISTANCE]['avg'] = array_sum($linkLengths) / $countBacklinks; 191 $statExport[Analytics::INTERNAL_LINK_DISTANCE]['max'] = max($linkLengths); 192 $statExport[Analytics::INTERNAL_LINK_DISTANCE]['min'] = min($linkLengths); 193 } 194 } 195 196 /** 197 * Quality Report / Rules 198 */ 199 // The array that hold the results of the quality rules 200 $ruleResults = array(); 201 // The array that hold the quality score details 202 $qualityScores = array(); 203 204 205 /** 206 * No fixme 207 */ 208 if (array_key_exists(self::FIXME, $this->stats)) { 209 $fixmeCount = $this->stats[self::FIXME]; 210 $statExport[self::FIXME] = $fixmeCount == null ? 0 : $fixmeCount; 211 if ($fixmeCount != 0) { 212 $ruleResults[self::RULE_FIXME] = self::FAILED; 213 $qualityScores['no_' . self::FIXME] = 0; 214 } else { 215 $ruleResults[self::RULE_FIXME] = self::PASSED; 216 $qualityScores['no_' . self::FIXME] = $this->getConf(self::CONF_QUALITY_SCORE_NO_FIXME, 1); 217 } 218 } 219 220 /** 221 * A title should be present 222 */ 223 $titleScore = $this->getConf(self::CONF_QUALITY_SCORE_TITLE_PRESENT, 10); 224 if (empty($this->analyticsMetadata[Analytics::TITLE])) { 225 $ruleResults[self::RULE_TITLE_PRESENT] = self::FAILED; 226 $ruleInfo[self::RULE_TITLE_PRESENT] = "Add a title in the frontmatter for {$titleScore} points"; 227 $this->analyticsMetadata[Analytics::TITLE] = $dokuWikiMetadata[Analytics::TITLE]; 228 $qualityScores[self::RULE_TITLE_PRESENT] = 0; 229 } else { 230 $qualityScores[self::RULE_TITLE_PRESENT] = $titleScore; 231 $ruleResults[self::RULE_TITLE_PRESENT] = self::PASSED; 232 } 233 234 /** 235 * A description should be present 236 */ 237 $descScore = $this->getConf(self::CONF_QUALITY_SCORE_DESCRIPTION_PRESENT, 8); 238 if (empty($this->analyticsMetadata[self::DESCRIPTION])) { 239 $ruleResults[self::RULE_DESCRIPTION_PRESENT] = self::FAILED; 240 $ruleInfo[self::RULE_DESCRIPTION_PRESENT] = "Add a description in the frontmatter for {$descScore} points"; 241 $this->analyticsMetadata[self::DESCRIPTION] = $dokuWikiMetadata[self::DESCRIPTION]["abstract"]; 242 $qualityScores[self::RULE_DESCRIPTION_PRESENT] = 0; 243 } else { 244 $qualityScores[self::RULE_DESCRIPTION_PRESENT] = $descScore; 245 $ruleResults[self::RULE_DESCRIPTION_PRESENT] = self::PASSED; 246 } 247 248 /** 249 * A canonical should be present 250 */ 251 $canonicalScore = $this->getConf(self::CONF_QUALITY_SCORE_CANONICAL_PRESENT, 5); 252 if (empty($this->analyticsMetadata[Page::CANONICAL_PROPERTY])) { 253 global $conf; 254 $root = $conf['start']; 255 if ($ID != $root) { 256 $qualityScores[self::RULE_CANONICAL_PRESENT] = 0; 257 $ruleResults[self::RULE_CANONICAL_PRESENT] = self::FAILED; 258 $ruleInfo[self::RULE_CANONICAL_PRESENT] = "Add a canonical in the frontmatter for {$canonicalScore} points"; 259 } 260 } else { 261 $qualityScores[self::RULE_CANONICAL_PRESENT] = $canonicalScore; 262 $ruleResults[self::RULE_CANONICAL_PRESENT] = self::PASSED; 263 } 264 265 /** 266 * Outline / Header structure 267 */ 268 $treeError = 0; 269 $headersCount = 0; 270 if (array_key_exists(Analytics::HEADER_POSITION, $this->stats)) { 271 $headersCount = count($this->stats[Analytics::HEADER_POSITION]); 272 unset($statExport[Analytics::HEADER_POSITION]); 273 for ($i = 1; $i < $headersCount; $i++) { 274 $currentHeaderLevel = $this->stats[self::HEADER_STRUCT][$i]; 275 $previousHeaderLevel = $this->stats[self::HEADER_STRUCT][$i - 1]; 276 if ($currentHeaderLevel - $previousHeaderLevel > 1) { 277 $treeError += 1; 278 $ruleInfo[self::RULE_OUTLINE_STRUCTURE] = "The " . $i . " header (h" . $currentHeaderLevel . ") has a level bigger than its precedent (" . $previousHeaderLevel . ")"; 279 } 280 } 281 unset($statExport[self::HEADER_STRUCT]); 282 } 283 $outlinePoints = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_HEADER_STRUCTURE, 3); 284 if ($treeError > 0 || $headersCount == 0) { 285 $qualityScores['correct_outline'] = 0; 286 $ruleResults[self::RULE_OUTLINE_STRUCTURE] = self::FAILED; 287 if ($headersCount == 0) { 288 $ruleInfo[self::RULE_OUTLINE_STRUCTURE] = "Add headings to create a document outline for {$outlinePoints} points"; 289 } 290 } else { 291 $qualityScores['correct_outline'] = $outlinePoints; 292 $ruleResults[self::RULE_OUTLINE_STRUCTURE] = self::PASSED; 293 } 294 295 296 /** 297 * Document length 298 */ 299 $minimalWordCount = 50; 300 $maximalWordCount = 1500; 301 $correctContentLength = true; 302 $correctLengthScore = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_CONTENT, 10); 303 $missingWords = $minimalWordCount - $statExport[Analytics::WORDS_COUNT]; 304 if ($missingWords > 0) { 305 $ruleResults[self::RULE_WORDS_MINIMAL] = self::FAILED; 306 $correctContentLength = false; 307 $ruleInfo[self::RULE_WORDS_MINIMAL] = "Add {$missingWords} words to get {$correctLengthScore} points"; 308 } else { 309 $ruleResults[self::RULE_WORDS_MINIMAL] = self::PASSED; 310 } 311 $tooMuchWords = $statExport[Analytics::WORDS_COUNT] - $maximalWordCount; 312 if ($tooMuchWords > 0) { 313 $ruleResults[self::RULE_WORDS_MAXIMAL] = self::FAILED; 314 $ruleInfo[self::RULE_WORDS_MAXIMAL] = "Delete {$tooMuchWords} words to get {$correctLengthScore} points"; 315 $correctContentLength = false; 316 } else { 317 $ruleResults[self::RULE_WORDS_MAXIMAL] = self::PASSED; 318 } 319 if ($correctContentLength) { 320 $qualityScores['correct_content_length'] = $correctLengthScore; 321 } else { 322 $qualityScores['correct_content_length'] = 0; 323 } 324 325 326 /** 327 * Average Number of words by header section to text ratio 328 */ 329 $headers = $this->stats[Analytics::HEADERS_COUNT]; 330 if ($headers != null) { 331 $headerCount = array_sum($headers); 332 $headerCount--; // h1 is supposed to have no words 333 if ($headerCount > 0) { 334 335 $avgWordsCountBySection = round($this->stats[Analytics::WORDS_COUNT] / $headerCount); 336 $statExport['word_section_count']['avg'] = $avgWordsCountBySection; 337 338 /** 339 * Min words by header section 340 */ 341 $wordsByHeaderMin = 20; 342 /** 343 * Max words by header section 344 */ 345 $wordsByHeaderMax = 300; 346 $correctAverageWordsBySection = true; 347 if ($avgWordsCountBySection < $wordsByHeaderMin) { 348 $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = self::FAILED; 349 $correctAverageWordsBySection = false; 350 $ruleInfo[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = "The number of words by section is less than {$wordsByHeaderMin}"; 351 } else { 352 $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MIN] = self::PASSED; 353 } 354 if ($avgWordsCountBySection > $wordsByHeaderMax) { 355 $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = self::FAILED; 356 $correctAverageWordsBySection = false; 357 $ruleInfo[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = "The number of words by section is more than {$wordsByHeaderMax}"; 358 } else { 359 $ruleResults[self::RULE_AVERAGE_WORDS_BY_SECTION_MAX] = self::PASSED; 360 } 361 if ($correctAverageWordsBySection) { 362 $qualityScores['correct_word_avg_by_section'] = $this->getConf(self::CONF_QUALITY_SCORE_CORRECT_WORD_SECTION_AVERAGE, 10); 363 } else { 364 $qualityScores['correct_word_avg_by_section'] = 0; 365 } 366 367 } 368 } 369 370 /** 371 * Internal Backlinks rule 372 * 373 * If a page is a low quality page, if the process run 374 * anonymous, we will not see all {@link ft_backlinks()} 375 * we use then the index directly to avoid confusion 376 */ 377 $backlinks = idx_get_indexer()->lookupKey('relation_references', $ID); 378 $countBacklinks = count($backlinks); 379 $statExport[Analytics::INTERNAL_BACKLINKS_COUNT] = $countBacklinks; 380 $backlinkScore = $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_BACKLINK_FACTOR, 1); 381 if ($countBacklinks == 0) { 382 $qualityScores[Analytics::INTERNAL_BACKLINKS_COUNT] = 0; 383 $ruleResults[self::RULE_INTERNAL_BACKLINKS_MIN] = self::FAILED; 384 $ruleInfo[self::RULE_INTERNAL_BACKLINKS_MIN] = "Add backlinks for {$backlinkScore} point each"; 385 } else { 386 387 $qualityScores[Analytics::INTERNAL_BACKLINKS_COUNT] = $countBacklinks * $backlinkScore; 388 $ruleResults[self::RULE_INTERNAL_BACKLINKS_MIN] = self::PASSED; 389 } 390 391 /** 392 * Internal links 393 */ 394 $internalLinksCount = $this->stats[Analytics::INTERNAL_LINKS_COUNT]; 395 $internalLinkScore = $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_LINK_FACTOR, 1); 396 if ($internalLinksCount == 0) { 397 $qualityScores[Analytics::INTERNAL_LINKS_COUNT] = 0; 398 $ruleResults[self::RULE_INTERNAL_LINKS_MIN] = self::FAILED; 399 $ruleInfo[self::RULE_INTERNAL_LINKS_MIN] = "Add internal links for {$internalLinkScore} point each"; 400 } else { 401 $ruleResults[self::RULE_INTERNAL_LINKS_MIN] = self::PASSED; 402 $qualityScores[Analytics::INTERNAL_LINKS_COUNT] = $countBacklinks * $internalLinkScore; 403 } 404 405 /** 406 * Broken Links 407 */ 408 $brokenLinkScore = $this->getConf(self::CONF_QUALITY_SCORE_INTERNAL_LINK_BROKEN_FACTOR, 2); 409 $brokenLinksCount = 0; 410 if (array_key_exists(Analytics::INTERNAL_LINKS_BROKEN_COUNT, $this->stats)) { 411 $brokenLinksCount = $this->stats[Analytics::INTERNAL_LINKS_BROKEN_COUNT]; 412 } 413 if ($brokenLinksCount > 2) { 414 $qualityScores['no_' . Analytics::INTERNAL_LINKS_BROKEN_COUNT] = 0; 415 $ruleResults[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = self::FAILED; 416 $ruleInfo[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = "Delete the {$brokenLinksCount} broken links and add {$brokenLinkScore} points"; 417 } else { 418 $qualityScores['no_' . Analytics::INTERNAL_LINKS_BROKEN_COUNT] = $brokenLinkScore; 419 $ruleResults[self::RULE_INTERNAL_BROKEN_LINKS_MAX] = self::PASSED; 420 } 421 422 /** 423 * Changes, the more changes the better 424 */ 425 $qualityScores[Analytics::EDITS_COUNT] = $statExport[Analytics::EDITS_COUNT] * $this->getConf(self::CONF_QUALITY_SCORE_CHANGES_FACTOR, 0.25); 426 427 428 /** 429 * Quality Score 430 */ 431 ksort($qualityScores); 432 $qualityScoring = array(); 433 $qualityScoring[self::SCORE] = array_sum($qualityScores); 434 $qualityScoring["scores"] = $qualityScores; 435 436 437 /** 438 * The rule that if broken will set the quality level to low 439 */ 440 $brokenRules = array(); 441 foreach ($ruleResults as $ruleName => $ruleResult) { 442 if ($ruleResult == self::FAILED) { 443 $brokenRules[] = $ruleName; 444 } 445 } 446 $ruleErrorCount = sizeof($brokenRules); 447 if ($ruleErrorCount > 0) { 448 $qualityResult = $ruleErrorCount . " quality rules errors"; 449 } else { 450 $qualityResult = "All quality rules passed"; 451 } 452 453 /** 454 * Low level Computation 455 */ 456 $mandatoryRules = preg_split("/,/", $this->getConf(self::CONF_MANDATORY_QUALITY_RULES)); 457 $mandatoryRulesBroken = []; 458 foreach ($mandatoryRules as $lowLevelRule) { 459 if (in_array($lowLevelRule, $brokenRules)) { 460 $mandatoryRulesBroken[] = $lowLevelRule; 461 } 462 } 463 /** 464 * If the low level is not set manually 465 */ 466 if (empty($this->analyticsMetadata[Page::LOW_QUALITY_PAGE_INDICATOR])) { 467 $lowLevel = false; 468 if (sizeof($mandatoryRulesBroken) > 0) { 469 $lowLevel = true; 470 } 471 } else { 472 $lowLevel = filter_var($this->analyticsMetadata[Page::LOW_QUALITY_PAGE_INDICATOR], FILTER_VALIDATE_BOOLEAN); 473 } 474 if (!$this->page->isBar()) { 475 $this->page->setLowQualityIndicator($lowLevel); 476 } else { 477 $this->page->setLowQualityIndicator(false); 478 } 479 480 /** 481 * Building the quality object in order 482 */ 483 $quality[Analytics::LOW] = $lowLevel; 484 if (sizeof($mandatoryRulesBroken) > 0) { 485 ksort($mandatoryRulesBroken); 486 $quality[Analytics::FAILED_MANDATORY_RULES] = $mandatoryRulesBroken; 487 } 488 $quality[self::SCORING] = $qualityScoring; 489 $quality[Analytics::RULES][self::RESULT] = $qualityResult; 490 if (!empty($ruleInfo)) { 491 $quality[Analytics::RULES]["info"] = $ruleInfo; 492 } 493 494 ksort($ruleResults); 495 $quality[Analytics::RULES][Analytics::DETAILS] = $ruleResults; 496 497 /** 498 * Metadata 499 */ 500 $title = $dokuWikiMetadata['title']; 501 $this->analyticsMetadata[Analytics::TITLE] = $title; 502 if ($title != @$dokuWikiMetadata['h1']) { 503 $this->analyticsMetadata[Analytics::H1] = $dokuWikiMetadata['h1']; 504 } 505 $timestampCreation = $dokuWikiMetadata['date']['created']; 506 $this->analyticsMetadata[self::DATE_CREATED] = date('Y-m-d h:i:s', $timestampCreation); 507 $timestampModification = $dokuWikiMetadata['date']['modified']; 508 $this->analyticsMetadata[Analytics::DATE_MODIFIED] = date('Y-m-d h:i:s', $timestampModification); 509 $this->analyticsMetadata['age_creation'] = round((time() - $timestampCreation) / 60 / 60 / 24); 510 $this->analyticsMetadata['age_modification'] = round((time() - $timestampModification) / 60 / 60 / 24); 511 512 513 /** 514 * Building the Top JSON in order 515 */ 516 global $ID; 517 $finalStats = array(); 518 $finalStats["id"] = $ID; 519 $finalStats["date"] = date('Y-m-d H:i:s', time()); 520 $finalStats['metadata'] = $this->analyticsMetadata; 521 ksort($statExport); 522 $finalStats[Analytics::STATISTICS] = $statExport; 523 $finalStats[Analytics::QUALITY] = $quality; // Quality after the sort to get them at the end 524 525 526 /** 527 * The result can be seen with 528 * doku.php?id=somepage&do=export_combo_analysis 529 * 530 * Set the header temporarily for the export.php file 531 */ 532 p_set_metadata( 533 $ID, 534 array("format" => array("combo_" . $this->getPluginComponent() => array("Content-Type" => 'application/json'))), 535 false, 536 false // Persistence is not needed, this is just in case this is an export 537 ); 538 $json_encoded = json_encode($finalStats, JSON_PRETTY_PRINT); 539 540 $this->page->saveAnalytics($finalStats); 541 $this->doc .= $json_encoded; 542 543 } 544 545 /** 546 */ 547 public function getFormat() 548 { 549 return Analytics::RENDERER_FORMAT; 550 } 551 552 public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') 553 { 554 555 $link = new LinkUtility($id); 556 $link->setType(LinkUtility::TYPE_INTERNAL); 557 $link->processLinkStats($this->stats); 558 559 } 560 561 public function externallink($url, $name = null) 562 { 563 $link = new LinkUtility($url); 564 $link->setType(LinkUtility::TYPE_EXTERNAL); 565 if ($name != null) { 566 $link->setName($name); 567 } 568 $link->processLinkStats($this->stats); 569 } 570 571 public function header($text, $level, $pos) 572 { 573 if (!array_key_exists(Analytics::HEADERS_COUNT, $this->stats)) { 574 $this->stats[Analytics::HEADERS_COUNT] = []; 575 } 576 $heading = 'h' . $level; 577 if (!array_key_exists( 578 $heading, 579 $this->stats[Analytics::HEADERS_COUNT])) { 580 $this->stats[Analytics::HEADERS_COUNT][$heading] = 0; 581 } 582 $this->stats[Analytics::HEADERS_COUNT][$heading]++; 583 584 $this->headerId++; 585 $this->stats[Analytics::HEADER_POSITION][$this->headerId] = $heading; 586 587 /** 588 * Store the level of each heading 589 * They should only go from low to highest value 590 * for a good outline 591 */ 592 if (!array_key_exists(Analytics::HEADERS_COUNT, $this->stats)) { 593 $this->stats[self::HEADER_STRUCT] = []; 594 } 595 $this->stats[self::HEADER_STRUCT][] = $level; 596 597 } 598 599 public function smiley($smiley) 600 { 601 if ($smiley == 'FIXME') $this->stats[self::FIXME]++; 602 } 603 604 public function linebreak() 605 { 606 if (!$this->tableopen) { 607 $this->stats['linebreak']++; 608 } 609 } 610 611 public function table_open($maxcols = null, $numrows = null, $pos = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 612 { 613 $this->tableopen = true; 614 } 615 616 public function table_close($pos = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 617 { 618 $this->tableopen = false; 619 } 620 621 public function hr() 622 { 623 $this->stats['hr']++; 624 } 625 626 public function quote_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 627 { 628 $this->stats['quote_count']++; 629 $this->quotelevel++; 630 $this->stats['quote_nest'] = max($this->quotelevel, $this->stats['quote_nest']); 631 } 632 633 public function quote_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 634 { 635 $this->quotelevel--; 636 } 637 638 public function strong_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 639 { 640 $this->formattingBracket++; 641 } 642 643 public function strong_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 644 { 645 $this->formattingBracket--; 646 } 647 648 public function emphasis_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 649 { 650 $this->formattingBracket++; 651 } 652 653 public function emphasis_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 654 { 655 $this->formattingBracket--; 656 } 657 658 public function underline_open() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 659 { 660 $this->formattingBracket++; 661 } 662 663 public function underline_close() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps 664 { 665 $this->formattingBracket--; 666 } 667 668 public function cdata($text) 669 { 670 671 /** 672 * It seems that you receive cdata 673 * when emphasis_open / underline_open / strong_open 674 * Stats are not for them 675 */ 676 if (!$this->formattingBracket) return; 677 678 $this->plainTextId++; 679 680 /** 681 * Length 682 */ 683 $len = strlen($text); 684 $this->stats[self::PLAINTEXT][$this->plainTextId]['len'] = $len; 685 686 687 /** 688 * Multi-formatting 689 */ 690 if ($this->formattingBracket > 1) { 691 $numberOfFormats = 1 * ($this->formattingBracket - 1); 692 $this->stats[self::PLAINTEXT][$this->plainTextId]['multiformat'] += $numberOfFormats; 693 } 694 695 /** 696 * Total 697 */ 698 $this->stats[self::PLAINTEXT][0] += $len; 699 } 700 701 public function internalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null) 702 { 703 $this->stats[Analytics::INTERNAL_MEDIAS_COUNT]++; 704 } 705 706 public function externalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null) 707 { 708 $this->stats[Analytics::EXTERNAL_MEDIAS]++; 709 } 710 711 public function reset() 712 { 713 $this->stats = array(); 714 $this->analyticsMetadata = array(); 715 $this->headerId = 0; 716 } 717 718 public function setMeta($key, $value) 719 { 720 $this->analyticsMetadata[$key] = $value; 721 } 722 723 724} 725 726