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