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