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