xref: /plugin/elasticsearch/action/indexing.php (revision b30042c1e1ed4d5dd7667d1d25fccd0363de335f) !
1<?php
2/**
3 * DokuWiki Plugin elasticsearch (Action Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Kieback&Peter IT <it-support@kieback-peter.de>
7 * @author  Andreas Gohr <gohr@cosmocode.de>
8 */
9
10// must be run within Dokuwiki
11if(!defined('DOKU_INC')) die();
12
13class action_plugin_elasticsearch_indexing extends DokuWiki_Action_Plugin {
14
15    /**
16     * Registers a callback function for a given event
17     *
18     * @param Doku_Event_Handler $controller DokuWiki's event controller object
19     * @return void
20     */
21    public function register(Doku_Event_Handler $controller) {
22        $controller->register_hook('TPL_CONTENT_DISPLAY', 'BEFORE', $this, 'handle_tpl_content_display');
23        $controller->register_hook('IO_WIKIPAGE_WRITE', 'BEFORE', $this, 'handle_delete');
24    }
25
26    /**
27     * Add pages to index
28     *
29     * @param Doku_Event $event event object by reference
30     * @param mixed $param [the parameters passed as fifth argument to register_hook() when this
31     *                           handler was registered]
32     * @return void
33     */
34    public function handle_tpl_content_display(Doku_Event &$event, $param) {
35        global $ID, $INFO;
36        $logs   = array();
37        $logs[] = 'BEGIN content display';
38        $logs[] = metaFN($ID, '.elasticsearch_indexed');
39        $logs[] = wikiFN($ID);
40        $logs[] = wikiFN($INFO['id']);
41        $logs[] = $this->needs_indexing($ID) ? 'needs indexing' : 'no indexing needed';
42        $logs[] = 'END content display';
43        $this->log($logs);
44        if($this->needs_indexing($ID)) {
45            $this->index_page($ID);
46        }
47    }
48
49    /**
50     * Remove pages from index
51     *
52     * @param Doku_Event $event event object by reference
53     * @param mixed $param [the parameters passed as fifth argument to register_hook() when this
54     *                           handler was registered]
55     * @return void
56     */
57    public function handle_delete(Doku_Event &$event, $param) {
58        if($event->data[3]) return; // is old revision stuff
59        if(!empty($event->data[0][1])) return; // page still exists
60        // still here? delete from index
61        $this->delete_page($event->data[2]);
62    }
63
64    /**
65     * Check if the page $id has changed since the last indexing.
66     *
67     * @param string $id
68     * @return boolean
69     */
70    protected function needs_indexing($id) {
71        $indexStateFile = metaFN($id, '.elasticsearch_indexed');
72        $dataFile       = wikiFN($id);
73
74        // no data file -> no indexing
75        if(!file_exists($dataFile)) {
76            // page does not exist but has a state file, try to remove from index
77            if(file_exists($indexStateFile)) {
78                $this->delete_page($id);
79            }
80            return false;
81        }
82
83        // force indexing if we're called via cli (e.g. cron)
84        if(php_sapi_name() == 'cli') {
85            return true;
86        }
87        // check if latest indexing attempt is done after page update
88        if(file_exists($indexStateFile)) {
89            if(filemtime($indexStateFile) > filemtime($dataFile)) {
90                return false;
91            }
92        }
93        return true;
94    }
95
96    /**
97     * @param string $id
98     * @param array $data
99     */
100    protected function write_index($id, $data)
101    {
102        /** @var helper_plugin_elasticsearch_client $hlp */
103        $hlp = plugin_load('helper', 'elasticsearch_client');
104
105        $indexName    = $this->getConf('indexname');
106        $documentType = $this->getConf('documenttype');
107        $client       = $hlp->connect();
108        $index        = $client->getIndex($indexName);
109        $type         = $index->getType($documentType);
110        $documentId   = $documentType . '_' . $id;
111
112        // check if the document still exists to update it or add it as a new one
113        try {
114            $client->updateDocument($documentId, ['doc' => $data], $index->getName(), $type->getName());
115        } catch (\Elastica\Exception\NotFoundException $e) {
116            $document = new \Elastica\Document($documentId, $data);
117            $type->addDocument($document);
118        } catch (\Elastica\Exception\ResponseException $e) {
119            if ($e->getResponse()->getStatus() == 404) {
120                $document = new \Elastica\Document($documentId, $data);
121                $type->addDocument($document);
122            } else {
123                throw $e;
124            }
125        } catch (Exception $e) {
126            msg(
127                'Something went wrong on indexing please try again later or ask an admin for help.<br /><pre>' .
128                hsc(get_class($e) . ' ' . $e->getMessage()) . '</pre>',
129                -1
130            );
131            return;
132        }
133        $index->refresh();
134        $this->update_indexstate($id);
135    }
136
137    /**
138     * Save indexed state for a page
139     *
140     * @param string $id
141     * @return int
142     */
143    protected function update_indexstate($id) {
144        $indexStateFile = metaFN($id, '.elasticsearch_indexed');
145        return io_saveFile($indexStateFile, '');
146    }
147
148    /**
149     * Remove the given document from the index
150     *
151     * @param $id
152     */
153    public function delete_page($id) {
154        /** @var helper_plugin_elasticsearch_client $hlp */
155        $hlp          = plugin_load('helper', 'elasticsearch_client');
156        $indexName    = $this->getConf('indexname');
157        $documentType = $this->getConf('documenttype');
158        $client       = $hlp->connect();
159        $index        = $client->getIndex($indexName);
160        $type         = $index->getType($documentType);
161        $documentId   = $documentType . '_' . $id;
162
163        try {
164            $type->deleteById($documentId);
165            $index->refresh();
166            $this->log($documentId.' deleted ');
167        } catch(Exception $e) {
168            // we ignore this
169            $this->log($documentId.' not deleted '.$e->getMessage());
170        }
171
172        // delete state file
173        @unlink(metaFN($id, '.elasticsearch_indexed'));
174    }
175
176    /**
177     * Index a page
178     *
179     * @param $id
180     * @return void
181     */
182    public function index_page($id) {
183        global $conf;
184
185        /** @var helper_plugin_elasticsearch_acl $hlpAcl */
186        $hlpAcl = plugin_load('helper', 'elasticsearch_acl');
187
188        $this->log('Indexing page ' . $id);
189
190        // collect the date which should be indexed
191        $meta = p_get_metadata($id, '', METADATA_RENDER_UNLIMITED);
192
193        $data             = array();
194        $data['uri']      = $id;
195        $data['created']  = date('Y-m-d\TH:i:s\Z', $meta['date']['created']);
196        $data['modified'] = date('Y-m-d\TH:i:s\Z', $meta['date']['modified']);
197        $data['user']     = $meta['user'];
198        $data['title']    = $meta['title'];
199        $data['abstract'] = $meta['description']['abstract'];
200        $data['content']  = rawWiki($id);
201
202        /** @var helper_plugin_translation $trans */
203        $trans = plugin_load('helper', 'translation');
204        if($trans) {
205            // translation plugin available
206            $lc               = $trans->getLangPart($id);
207            $data['language'] = $trans->realLC($lc);
208        } else {
209            // no translation plugin
210            $lc               = '';
211            $data['language'] = $conf['lang'];
212        }
213
214        $data['namespace'] = getNS($id);
215        if(trim($data['namespace']) == '') {
216            unset($data['namespace']);
217        }
218
219        $fullACL = $hlpAcl->getPageACL($id);
220        $queryACL = $hlpAcl->splitRules($fullACL);
221        $data = array_merge($data, $queryACL);
222        $this->write_index($id, $data);
223    }
224
225    /**
226     * Log something to the debug log
227     *
228     * @param string|string[] $txt
229     */
230    protected function log($txt) {
231        if (!$this->getConf('debug')) {
232            return;
233        }
234        $logs = (array)$txt;
235
236        foreach ($logs as $entry) {
237            dbglog($entry);
238        }
239    }
240}
241