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