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