1c25e802bSgerardnico<?php 2c25e802bSgerardnico 3db974367Sgerardnicouse Combostrap\AnalyticsMenuItem; 4*a6bf47aaSNickeauuse ComboStrap\Identity; 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/' . 'AnalyticsMenuItem.php'); 222c067407Sgerardnico 23c25e802bSgerardnico/** 24c25e802bSgerardnico * Class action_plugin_combo_analytics 25c25e802bSgerardnico * Update the analytics data 26c25e802bSgerardnico */ 27c25e802bSgerardnicoclass action_plugin_combo_analytics extends DokuWiki_Action_Plugin 28c25e802bSgerardnico{ 29c25e802bSgerardnico 305f891b7eSNickeau /** 315f891b7eSNickeau * @var array 325f891b7eSNickeau */ 335f891b7eSNickeau protected $linksBeforeByPage = array(); 34c25e802bSgerardnico 35c25e802bSgerardnico public function register(Doku_Event_Handler $controller) 36c25e802bSgerardnico { 372c067407Sgerardnico 38c25e802bSgerardnico /** 39c42a1196Sgerardnico * Analytics to refresh because they have lost or gain a backlinks 40c42a1196Sgerardnico * are done via Sqlite table (The INDEXER_TASKS_RUN gives a way to 41c42a1196Sgerardnico * manipulate this queue) 42266b617eSgerardnico * 43266b617eSgerardnico * There is no need to do it at page write 44266b617eSgerardnico * https://www.dokuwiki.org/devel:event:io_wikipage_write 45266b617eSgerardnico * because after the page is written, the page is shown and trigger the index tasks run 46d262537cSgerardnico * 47d262537cSgerardnico * We do it after because if there is an error 48d262537cSgerardnico * We will not stop the Dokuwiki Processing 49c42a1196Sgerardnico */ 50d262537cSgerardnico $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, 'handle_refresh_analytics', array()); 5155d4462bSgerardnico 52db974367Sgerardnico /** 53db974367Sgerardnico * Add a icon in the page tools menu 54db974367Sgerardnico * https://www.dokuwiki.org/devel:event:menu_items_assembly 55db974367Sgerardnico */ 56db974367Sgerardnico $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'handle_page_tools'); 57db974367Sgerardnico 585f891b7eSNickeau /** 595f891b7eSNickeau * Check the internal link that have been 605f891b7eSNickeau * added or deleted to update the backlinks statistics 615f891b7eSNickeau * if a link has been added or deleted 625f891b7eSNickeau */ 635f891b7eSNickeau $controller->register_hook('PARSER_METADATA_RENDER', 'AFTER', $this, 'handle_meta_renderer_after', array()); 645f891b7eSNickeau $controller->register_hook('PARSER_METADATA_RENDER', 'BEFORE', $this, 'handle_meta_renderer_before', array()); 655f891b7eSNickeau 66c25e802bSgerardnico } 67c25e802bSgerardnico 6855d4462bSgerardnico public function handle_refresh_analytics(Doku_Event $event, $param) 6955d4462bSgerardnico { 7055d4462bSgerardnico 71c42a1196Sgerardnico /** 72c42a1196Sgerardnico * Check that the actual page has analytics data 73c42a1196Sgerardnico * (if there is a cache, it's pretty quick) 74c42a1196Sgerardnico */ 75c42a1196Sgerardnico global $ID; 765f891b7eSNickeau if ($ID == null) { 775f891b7eSNickeau $id = $event->data['page']; 785f891b7eSNickeau } else { 795f891b7eSNickeau $id = $ID; 805f891b7eSNickeau } 815f891b7eSNickeau $page = new Page($id); 82d81822adSgerardnico if ($page->shouldAnalyticsProcessOccurs()) { 837c33ecc6Sgerardnico $page->processAnalytics(); 8455d4462bSgerardnico } 8555d4462bSgerardnico 86d81822adSgerardnico /** 87d81822adSgerardnico * Process the analytics to refresh 88d81822adSgerardnico */ 89d81822adSgerardnico $this->analyticsBatchBackgroundRefresh(); 90d81822adSgerardnico 9155d4462bSgerardnico } 92db974367Sgerardnico 93d81822adSgerardnico public function handle_page_tools(Doku_Event $event, $param) 94d81822adSgerardnico { 95db974367Sgerardnico 96*a6bf47aaSNickeau if (!Identity::isWriter()) { 97db974367Sgerardnico return; 98db974367Sgerardnico } 99db974367Sgerardnico 100db974367Sgerardnico /** 101db974367Sgerardnico * The `view` property defines the menu that is currently built 102db974367Sgerardnico * https://www.dokuwiki.org/devel:menus 103db974367Sgerardnico * If this is not the page menu, return 104db974367Sgerardnico */ 105db974367Sgerardnico if ($event->data['view'] != 'page') return; 106db974367Sgerardnico 107db974367Sgerardnico global $INFO; 108db974367Sgerardnico if (!$INFO['exists']) { 109db974367Sgerardnico return; 110db974367Sgerardnico } 111db974367Sgerardnico array_splice($event->data['items'], -1, 0, array(new AnalyticsMenuItem())); 112db974367Sgerardnico 113db974367Sgerardnico } 114d81822adSgerardnico 115d81822adSgerardnico private function analyticsBatchBackgroundRefresh() 116d81822adSgerardnico { 117d81822adSgerardnico $sqlite = Sqlite::getSqlite(); 118d81822adSgerardnico $res = $sqlite->query("SELECT ID FROM ANALYTICS_TO_REFRESH"); 119d81822adSgerardnico if (!$res) { 120d81822adSgerardnico LogUtility::msg("There was a problem during the select: {$sqlite->getAdapter()->getDb()->errorInfo()}"); 121d81822adSgerardnico } 122d81822adSgerardnico $rows = $sqlite->res2arr($res, true); 123d81822adSgerardnico $sqlite->res_close($res); 124d81822adSgerardnico 125d81822adSgerardnico /** 126d81822adSgerardnico * In case of a start or if there is a recursive bug 127d81822adSgerardnico * We don't want to take all the resources 128d81822adSgerardnico */ 1295f891b7eSNickeau $maxRefresh = 10; // by default, there is 5 pages in a default dokuwiki installation in the wiki namespace 130d81822adSgerardnico $maxRefreshLow = 2; 1315f891b7eSNickeau $pagesToRefresh = sizeof($rows); 1325f891b7eSNickeau if ($pagesToRefresh > $maxRefresh) { 1335f891b7eSNickeau 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"); 134d81822adSgerardnico $maxRefresh = $maxRefreshLow; 135d81822adSgerardnico } 136d81822adSgerardnico $refreshCounter = 0; 137d81822adSgerardnico foreach ($rows as $row) { 138d81822adSgerardnico $page = new Page($row['ID']); 139d81822adSgerardnico $page->processAnalytics(); 140d81822adSgerardnico $refreshCounter++; 141d81822adSgerardnico if ($refreshCounter >= $maxRefresh) { 142d81822adSgerardnico break; 143d81822adSgerardnico } 144d81822adSgerardnico } 145d81822adSgerardnico 146d81822adSgerardnico } 1475f891b7eSNickeau 1485f891b7eSNickeau /** 1495f891b7eSNickeau * Generate the statistics for the internal link added or deleted 1505f891b7eSNickeau * 1515f891b7eSNickeau * @param Doku_Event $event 1525f891b7eSNickeau * @param $param 1535f891b7eSNickeau */ 1545f891b7eSNickeau public function handle_meta_renderer_before(Doku_Event $event, $param) 1555f891b7eSNickeau { 1565f891b7eSNickeau 1575f891b7eSNickeau $pageId = $event->data['page']; 1585f891b7eSNickeau $page = new Page($pageId); 1595f891b7eSNickeau $links = $page->getInternalLinksFromMeta(); 1605f891b7eSNickeau if ($links !== null) { 1615f891b7eSNickeau $this->linksBeforeByPage[$pageId] = $links; 1625f891b7eSNickeau } else { 1635f891b7eSNickeau $this->linksBeforeByPage[$pageId] = array(); 1645f891b7eSNickeau } 1655f891b7eSNickeau 1665f891b7eSNickeau 1675f891b7eSNickeau } 1685f891b7eSNickeau 1695f891b7eSNickeau /** 1705f891b7eSNickeau * Save the links before metadata render 1715f891b7eSNickeau * @param Doku_Event $event 1725f891b7eSNickeau * @param $param 1735f891b7eSNickeau */ 1745f891b7eSNickeau public function handle_meta_renderer_after(Doku_Event $event, $param) 1755f891b7eSNickeau { 1765f891b7eSNickeau 1775f891b7eSNickeau $pageId = $event->data['page']; 1785f891b7eSNickeau $linksAfter = $event->data['current']['relation']['references']; 1795f891b7eSNickeau if ($linksAfter == null) { 1805f891b7eSNickeau $linksAfter = array(); 1815f891b7eSNickeau } 1825f891b7eSNickeau $linksBefore = $this->linksBeforeByPage[$pageId]; 1835f891b7eSNickeau unset($this->linksBeforeByPage[$pageId]); 1845f891b7eSNickeau $addedLinks = array(); 1855f891b7eSNickeau foreach ($linksAfter as $linkAfter => $exist) { 1865f891b7eSNickeau if (array_key_exists($linkAfter, $linksBefore)) { 1875f891b7eSNickeau unset($linksBefore[$linkAfter]); 1885f891b7eSNickeau } else { 1895f891b7eSNickeau $addedLinks[] = $linkAfter; 1905f891b7eSNickeau } 1915f891b7eSNickeau } 1925f891b7eSNickeau 1935f891b7eSNickeau /** 1945f891b7eSNickeau * Process to update the backlinks 1955f891b7eSNickeau */ 1965f891b7eSNickeau $linksChanged = array_fill_keys($addedLinks,"added"); 1975f891b7eSNickeau foreach ($linksBefore as $deletedLink => $deletedLinkPageExists) { 1985f891b7eSNickeau $linksChanged[$deletedLink] = 'deleted'; 1995f891b7eSNickeau } 2005f891b7eSNickeau foreach ($linksChanged as $changedLink => $status) { 2015f891b7eSNickeau /** 2025f891b7eSNickeau * We delete the cache 2035f891b7eSNickeau * We don't update the analytics 2045f891b7eSNickeau * because we want speed 2055f891b7eSNickeau */ 2065f891b7eSNickeau $addedPage = new Page($changedLink); 2075f891b7eSNickeau $reason = "The backlink {$changedLink} from the page {$pageId} was {$status}"; 2085f891b7eSNickeau $addedPage->deleteCacheAndAskAnalyticsRefresh($reason); 2095f891b7eSNickeau 2105f891b7eSNickeau } 2115f891b7eSNickeau 2125f891b7eSNickeau } 2135f891b7eSNickeau 2145f891b7eSNickeau 215c25e802bSgerardnico} 216c25e802bSgerardnico 217c25e802bSgerardnico 218c25e802bSgerardnico 219