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