xref: /plugin/elasticsearch/helper/client.php (revision a710a292c1358b472dae2333494a0d27cc1c00cb)
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