xref: /plugin/combo/cli.php (revision 21913ab3235d516e2fa19c7e3929b555b3a2bda1)
1<?php
2/**
3 * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved.
4 *
5 * This source code is licensed under the GPL license found in the
6 * COPYING  file in the root directory of this source tree.
7 *
8 * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
9 * @author   ComboStrap <support@combostrap.com>
10 *
11 */
12if (!defined('DOKU_INC')) die();
13
14use ComboStrap\Analytics;
15use ComboStrap\Page;
16use ComboStrap\PluginUtility;
17use ComboStrap\Sqlite;
18use splitbrain\phpcli\Options;
19
20require_once(__DIR__ . '/class/Analytics.php');
21
22/**
23 * The memory of the server 128 is not enough
24 */
25ini_set('memory_limit', '256M');
26
27/**
28 * Class cli_plugin_combo
29 *
30 * This is a cli:
31 * https://www.dokuwiki.org/devel:cli_plugins#example
32 *
33 * Usage:
34 *
35 * ```
36 * docker exec -ti $(CONTAINER) /bin/bash
37 * ./bin/plugin.php combo -c
38 * ```
39 * or via the IDE
40 *
41 *
42 * Example:
43 * https://www.dokuwiki.org/tips:grapher
44 *
45 */
46class cli_plugin_combo extends DokuWiki_CLI_Plugin
47{
48    const ANALYTICS = "analytics";
49    const SYNC = "sync";
50
51    /**
52     * register options and arguments
53     * @param Options $options
54     */
55    protected function setup(Options $options)
56    {
57        $options->setHelp(
58            "Manage the analytics database\n\n" .
59            "analytics\n" .
60            "sync"
61        );
62        $options->registerOption('version', 'print version', 'v');
63        $options->registerCommand(self::ANALYTICS, "Update the analytics data");
64        $options->registerOption(
65            'namespaces',
66            "If no namespace is given, the root namespace is assumed.",
67            'n',
68            true
69        );
70        $options->registerOption(
71            'output',
72            "Optional, where to store the analytical data as csv eg. a filename.",
73            'o', 'file');
74        $options->registerOption(
75            'cache',
76            "Optional, returns from the cache if set",
77            'c', false);
78        $options->registerOption(
79            'dry',
80            "Optional, dry-run",
81            'd', false);
82        $options->registerCommand(self::SYNC, "Sync the database");
83
84    }
85
86    /**
87     * The main entry
88     * @param Options $options
89     */
90    protected function main(Options $options)
91    {
92
93        $namespaces = array_map('cleanID', $options->getArgs());
94        if (!count($namespaces)) $namespaces = array(''); //import from top
95
96        $cache = $options->getOpt('cache', false);
97        $depth = $options->getOpt('depth', 0);
98        $cmd = $options->getCmd();
99        if ($cmd == "") {
100            $cmd = self::ANALYTICS;
101        }
102        switch ($cmd) {
103            case self::ANALYTICS:
104                $output = $options->getOpt('output', '');
105                //if ($output == '-') $output = 'php://stdout';
106                $this->updateAnalyticsData($namespaces, $output, $cache, $depth);
107                break;
108            case self::SYNC:
109                $this->syncPages();
110                break;
111            default:
112                throw new \RuntimeException("Combo: Command unknown (" . $cmd . ")");
113        }
114
115
116    }
117
118    /**
119     * @param array $namespaces
120     * @param $output
121     * @param bool $cache
122     * @param int $depth recursion depth. 0 for unlimited
123     */
124    private function updateAnalyticsData($namespaces = array(), $output = null, $cache = false, $depth = 0)
125    {
126
127        $fileHandle = null;
128        if (!empty($output)) {
129            $fileHandle = @fopen($output, 'w');
130            if (!$fileHandle) $this->fatal("Failed to open $output");
131        }
132
133        $pages = $this->findPages($namespaces, $depth);
134
135
136        if (!empty($fileHandle)) {
137            $header = array(
138                'id',
139                'backlinks',
140                'broken_links',
141                'changes',
142                'chars',
143                'external_links',
144                'external_medias',
145                'h1',
146                'h2',
147                'h3',
148                'h4',
149                'h5',
150                'internal_links',
151                'internal_medias',
152                'words',
153                'score'
154            );
155            fwrite($fileHandle, implode(",", $header) . PHP_EOL);
156        }
157        $pageCounter = 0;
158        while ($page = array_shift($pages)) {
159            $id = $page['id'];
160
161            $pageCounter++;
162            echo "Processing the page {$id} ($pageCounter)\n";
163
164            $data = Analytics::processAndGetDataAsArray($id, $cache);
165            if (!empty($fileHandle)) {
166                $statistics = $data[Analytics::STATISTICS];
167                $row = array(
168                    'id' => $id,
169                    'backlinks' => $statistics[Analytics::INTERNAL_BACKLINKS_COUNT],
170                    'broken_links' => $statistics[Analytics::INTERNAL_LINKS_BROKEN_COUNT],
171                    'changes' => $statistics[Analytics::EDITS_COUNT],
172                    'chars' => $statistics[Analytics::CHARS_COUNT],
173                    'external_links' => $statistics[Analytics::EXTERNAL_LINKS_COUNT],
174                    'external_medias' => $statistics[Analytics::EXTERNAL_MEDIAS],
175                    'h1' => $statistics[Analytics::HEADERS_COUNT]['h1'],
176                    'h2' => $statistics[Analytics::HEADERS_COUNT]['h2'],
177                    'h3' => $statistics[Analytics::HEADERS_COUNT]['h3'],
178                    'h4' => $statistics[Analytics::HEADERS_COUNT]['h4'],
179                    'h5' => $statistics[Analytics::HEADERS_COUNT]['h5'],
180                    'internal_links' => $statistics[Analytics::INTERNAL_LINKS_COUNT],
181                    'internal_medias' => $statistics[Analytics::INTERNAL_MEDIAS_COUNT],
182                    'words' => $statistics[Analytics::WORDS_COUNT],
183                    'low' => $data[Analytics::QUALITY]['low']
184                );
185                fwrite($fileHandle, implode(",", $row) . PHP_EOL);
186            }
187        }
188        if (!empty($fileHandle)) {
189            fclose($fileHandle);
190        }
191
192    }
193
194    /**
195     * Find the pages in the tree
196     * @param $namespaces
197     * @param $depth
198     * @return array
199     */
200    private function findPages($namespaces = array(), $depth = 0)
201    {
202        // Run as admin to overcome the fact that
203        // anonymous user cannot set all links and backlinnks
204
205
206        global $conf;
207        $datadir = $conf['datadir'];
208
209        /**
210         * Run as admin to overcome the fact that
211         * anonymous user cannot see all links and backlinnks
212         */
213        global $USERINFO;
214        $USERINFO['grps'] = array('admin');
215        global $INPUT;
216        $INPUT->server->set('REMOTE_USER', "cli");
217
218        $pages = array();
219        foreach ($namespaces as $ns) {
220
221            search(
222                $pages,
223                $datadir,
224                'search_universal',
225                array(
226                    'depth' => $depth,
227                    'listfiles' => true,
228                    'listdirs' => false,
229                    'pagesonly' => true,
230                    'skipacl' => true,
231                    'firsthead' => false,
232                    'meta' => false,
233                ),
234                str_replace(':', '/', $ns)
235            );
236
237            // add the ns start page
238            if ($ns && page_exists($ns)) {
239                $pages[] = array(
240                    'id' => $ns,
241                    'ns' => getNS($ns),
242                    'title' => p_get_first_heading($ns, false),
243                    'size' => filesize(wikiFN($ns)),
244                    'mtime' => filemtime(wikiFN($ns)),
245                    'perm' => 16,
246                    'type' => 'f',
247                    'level' => 0,
248                    'open' => 1,
249                );
250            }
251
252        }
253        return $pages;
254    }
255
256    private function syncPages()
257    {
258        $sqlite = Sqlite::getSqlite();
259        $res = $sqlite->query("select ID from pages");
260        if (!$res) {
261            throw new \RuntimeException("An exception has occurred with the alias selection query");
262        }
263        $res2arr = $sqlite->res2arr($res);
264        $sqlite->res_close($res);
265        foreach ($res2arr as $row) {
266            $id = $row['ID'];
267            if (!page_exists($id)) {
268                echo 'Page does not exist on the file system. Deleted from the database (' . $id . ")\n";
269                Page::createPageFromPathId($id)->deleteInDb();
270            }
271        }
272
273
274    }
275}
276