1<?php 2 3use Combostrap\AnalyticsMenuItem; 4use ComboStrap\Identity; 5use ComboStrap\LogUtility; 6use ComboStrap\Page; 7use ComboStrap\Sqlite; 8 9/** 10 * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 11 * 12 * This source code is licensed under the GPL license found in the 13 * COPYING file in the root directory of this source tree. 14 * 15 * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 16 * @author ComboStrap <support@combostrap.com> 17 * 18 */ 19 20require_once(__DIR__ . '/../class/' . 'Analytics.php'); 21require_once(__DIR__ . '/../class/' . 'AnalyticsMenuItem.php'); 22 23/** 24 * Class action_plugin_combo_analytics 25 * Update the analytics data 26 */ 27class action_plugin_combo_analytics extends DokuWiki_Action_Plugin 28{ 29 30 /** 31 * @var array 32 */ 33 protected $linksBeforeByPage = array(); 34 35 public function register(Doku_Event_Handler $controller) 36 { 37 38 /** 39 * Analytics to refresh because they have lost or gain a backlinks 40 * are done via Sqlite table (The INDEXER_TASKS_RUN gives a way to 41 * manipulate this queue) 42 * 43 * There is no need to do it at page write 44 * https://www.dokuwiki.org/devel:event:io_wikipage_write 45 * because after the page is written, the page is shown and trigger the index tasks run 46 * 47 * We do it after because if there is an error 48 * We will not stop the Dokuwiki Processing 49 */ 50 $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, 'handle_refresh_analytics', array()); 51 52 /** 53 * Add a icon in the page tools menu 54 * https://www.dokuwiki.org/devel:event:menu_items_assembly 55 */ 56 $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'handle_page_tools'); 57 58 /** 59 * Check the internal link that have been 60 * added or deleted to update the backlinks statistics 61 * if a link has been added or deleted 62 */ 63 $controller->register_hook('PARSER_METADATA_RENDER', 'AFTER', $this, 'handle_meta_renderer_after', array()); 64 $controller->register_hook('PARSER_METADATA_RENDER', 'BEFORE', $this, 'handle_meta_renderer_before', array()); 65 66 } 67 68 public function handle_refresh_analytics(Doku_Event $event, $param) 69 { 70 71 /** 72 * Check that the actual page has analytics data 73 * (if there is a cache, it's pretty quick) 74 */ 75 global $ID; 76 if ($ID == null) { 77 $id = $event->data['page']; 78 } else { 79 $id = $ID; 80 } 81 $page = new Page($id); 82 if ($page->shouldAnalyticsProcessOccurs()) { 83 $page->processAnalytics(); 84 } 85 86 /** 87 * Process the analytics to refresh 88 */ 89 $this->analyticsBatchBackgroundRefresh(); 90 91 } 92 93 public function handle_page_tools(Doku_Event $event, $param) 94 { 95 96 if (!Identity::isWriter()) { 97 return; 98 } 99 100 /** 101 * The `view` property defines the menu that is currently built 102 * https://www.dokuwiki.org/devel:menus 103 * If this is not the page menu, return 104 */ 105 if ($event->data['view'] != 'page') return; 106 107 global $INFO; 108 if (!$INFO['exists']) { 109 return; 110 } 111 array_splice($event->data['items'], -1, 0, array(new AnalyticsMenuItem())); 112 113 } 114 115 private function analyticsBatchBackgroundRefresh() 116 { 117 $sqlite = Sqlite::getSqlite(); 118 $res = $sqlite->query("SELECT ID FROM ANALYTICS_TO_REFRESH"); 119 if (!$res) { 120 LogUtility::msg("There was a problem during the select: {$sqlite->getAdapter()->getDb()->errorInfo()}"); 121 } 122 $rows = $sqlite->res2arr($res, true); 123 $sqlite->res_close($res); 124 125 /** 126 * In case of a start or if there is a recursive bug 127 * We don't want to take all the resources 128 */ 129 $maxRefresh = 10; // by default, there is 5 pages in a default dokuwiki installation in the wiki namespace 130 $maxRefreshLow = 2; 131 $pagesToRefresh = sizeof($rows); 132 if ($pagesToRefresh > $maxRefresh) { 133 LogUtility::msg("There is {$pagesToRefresh} pages to refresh in the queue (table `ANALYTICS_TO_REFRESH`). This is more than {$maxRefresh} pages. Batch background Analytics refresh was reduced to {$maxRefreshLow} pages to not hit the computer resources.", LogUtility::LVL_MSG_ERROR, "analytics"); 134 $maxRefresh = $maxRefreshLow; 135 } 136 $refreshCounter = 0; 137 foreach ($rows as $row) { 138 $page = new Page($row['ID']); 139 $page->processAnalytics(); 140 $refreshCounter++; 141 if ($refreshCounter >= $maxRefresh) { 142 break; 143 } 144 } 145 146 } 147 148 /** 149 * Generate the statistics for the internal link added or deleted 150 * 151 * @param Doku_Event $event 152 * @param $param 153 */ 154 public function handle_meta_renderer_before(Doku_Event $event, $param) 155 { 156 157 $pageId = $event->data['page']; 158 $page = new Page($pageId); 159 $links = $page->getInternalLinksFromMeta(); 160 if ($links !== null) { 161 $this->linksBeforeByPage[$pageId] = $links; 162 } else { 163 $this->linksBeforeByPage[$pageId] = array(); 164 } 165 166 167 } 168 169 /** 170 * Save the links before metadata render 171 * @param Doku_Event $event 172 * @param $param 173 */ 174 public function handle_meta_renderer_after(Doku_Event $event, $param) 175 { 176 177 $pageId = $event->data['page']; 178 $linksAfter = $event->data['current']['relation']['references']; 179 if ($linksAfter == null) { 180 $linksAfter = array(); 181 } 182 $linksBefore = $this->linksBeforeByPage[$pageId]; 183 unset($this->linksBeforeByPage[$pageId]); 184 $addedLinks = array(); 185 foreach ($linksAfter as $linkAfter => $exist) { 186 if (array_key_exists($linkAfter, $linksBefore)) { 187 unset($linksBefore[$linkAfter]); 188 } else { 189 $addedLinks[] = $linkAfter; 190 } 191 } 192 193 /** 194 * Process to update the backlinks 195 */ 196 $linksChanged = array_fill_keys($addedLinks,"added"); 197 foreach ($linksBefore as $deletedLink => $deletedLinkPageExists) { 198 $linksChanged[$deletedLink] = 'deleted'; 199 } 200 foreach ($linksChanged as $changedLink => $status) { 201 /** 202 * We delete the cache 203 * We don't update the analytics 204 * because we want speed 205 */ 206 $addedPage = new Page($changedLink); 207 $reason = "The backlink {$changedLink} from the page {$pageId} was {$status}"; 208 $addedPage->deleteCacheAndAskAnalyticsRefresh($reason); 209 210 } 211 212 } 213 214 215} 216 217 218 219