1<?php 2/** 3 * DokuWiki Plugin elasticsearch (Helper 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 */ 8 9// must be run within Dokuwiki 10if(!defined('DOKU_INC')) die(); 11require_once dirname(__FILE__) . '/../vendor/autoload.php'; 12 13/** 14 * Access to the Elastica client 15 */ 16class helper_plugin_elasticsearch_client extends DokuWiki_Plugin { 17 18 /** @var array Map of ISO codes to Elasticsearch analyzer names */ 19 const ANALYZERS = [ 20 'ar' => 'arabic', 21 'bg' => 'bulgarian', 22 'bn' => 'bengali', 23 'ca' => 'catalan', 24 'cs' => 'czech', 25 'da' => 'danish', 26 'de' => 'german', 27 'el' => 'greek', 28 'en' => 'english', 29 'es' => 'spanish', 30 'eu' => 'basque', 31 'fa' => 'persian', 32 'fi' => 'finnish', 33 'fr' => 'french', 34 'ga' => 'irish', 35 'gl' => 'galician', 36 'hi' => 'hindi', 37 'hu' => 'hungarian', 38 'hy' => 'armenian', 39 'id' => 'indonesian', 40 'it' => 'italian', 41 'lt' => 'lithuanian', 42 'lv' => 'latvian', 43 'nl' => 'dutch', 44 'no' => 'norwegian', 45 'pt' => 'portuguese', 46 'ro' => 'romanian', 47 'ru' => 'russian', 48 'sv' => 'swedish', 49 'th' => 'thai', 50 'tr' => 'turkish', 51 ]; 52 /** 53 * @var \Elastica\Client $elasticaClient 54 */ 55 protected $elasticaClient = null; 56 57 /** 58 * Connects to the elastica servers and returns the client object 59 * 60 * @return \Elastica\Client 61 */ 62 public function connect() { 63 if(!is_null($this->elasticaClient)) return $this->elasticaClient; 64 65 // parse servers config into DSN array 66 $dsn = ['servers' => []]; 67 $servers = $this->getConf('servers'); 68 $lines = explode("\n", $servers); 69 foreach($lines as $line) { 70 list($host, $proxy) = explode(',', $line, 2); 71 list($host, $port) = explode(':', $host, 2); 72 $host = trim($host); 73 $port = (int) trim($port); 74 if(!$port) $port = 80; 75 $proxy = trim($proxy); 76 if(!$host) continue; 77 $dsn['servers'][] = compact('host', 'port', 'proxy'); 78 } 79 80 $this->elasticaClient = new \Elastica\Client($dsn); 81 return $this->elasticaClient; 82 } 83 84 /** 85 * Create the index 86 * 87 * @param bool $clear rebuild index 88 * @return \Elastica\Response 89 */ 90 public function createIndex($clear=false) { 91 $client = $this->connect(); 92 $index = $client->getIndex($this->getConf('indexname')); 93 94 $index->create([], $clear); 95 96 return $this->mapAccessFields($index); 97 } 98 99 /** 100 * Create the field mapping: language analyzers for the content field 101 * 102 * @return \Elastica\Response 103 */ 104 public function createLanguageMapping() { 105 global $conf; 106 107 $client = $this->connect(); 108 $index = $client->getIndex($this->getConf('indexname')); 109 $type = $index->getType($this->getConf('documenttype')); 110 111 // default language 112 $props = [ 113 'content' => [ 114 'type' => 'text', 115 'fields' => [ 116 $conf['lang'] => [ 117 'type' => 'text', 118 'analyzer' => $this->getLanguageAnalyzer($conf['lang']) 119 ], 120 ] 121 ] 122 ]; 123 124 // other languages as configured in the translation plugin 125 /** @var helper_plugin_translation $transplugin */ 126 $transplugin = plugin_load('helper', 'translation'); 127 if ($transplugin) { 128 $translations = array_diff(array_filter($transplugin->translations), [$conf['lang']]); 129 if ($translations) foreach ($translations as $lang) { 130 $props['content']['fields'][$lang] = [ 131 'type' => 'text', 132 'analyzer' => $this->getLanguageAnalyzer($lang) 133 ]; 134 } 135 } 136 137 $mapping = new \Elastica\Type\Mapping(); 138 $mapping->setType($type); 139 $mapping->setProperties($props); 140 $response = $mapping->send(); 141 return $response; 142 } 143 144 /** 145 * Get the correct analyzer for the given language code 146 * 147 * Returns the standard analalyzer for unknown languages 148 * 149 * @param string $lang 150 * @return string 151 */ 152 protected function getLanguageAnalyzer($lang) 153 { 154 if (isset(self::ANALYZERS[$lang])) return self::ANALYZERS[$lang]; 155 return 'standard'; 156 } 157 158 /** 159 * Define special mappings for ACL fields 160 * 161 * Standard mapping could break the search because ACL fields 162 * might contain word-split tokens such as underscores and so must not 163 * be indexed using the standard text analyzer. 164 * 165 * @param \Elastica\Index $index 166 * @return \Elastica\Response 167 */ 168 protected function mapAccessFields(\Elastica\Index $index): \Elastica\Response 169 { 170 $type = $index->getType($this->getConf('documenttype')); 171 $props = [ 172 'groups_include' => [ 173 'type' => 'keyword', 174 ], 175 'groups_exclude' => [ 176 'type' => 'keyword', 177 ], 178 'users_include' => [ 179 'type' => 'keyword', 180 ], 181 'users_exclude' => [ 182 'type' => 'keyword', 183 ], 184 ]; 185 186 $mapping = new \Elastica\Type\Mapping(); 187 $mapping->setType($type); 188 $mapping->setProperties($props); 189 return $mapping->send(); 190 } 191} 192 193// vim:ts=4:sw=4:et: 194