xref: /template/strap/action/analytics.php (revision 5f891b7e09648e05e78f5882f3fdde1e9df9b0f1)
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