xref: /plugin/combo/action/analytics.php (revision a6bf47aa01c4ea7d24944b0e48eb1943151e3c25)
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