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