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