Skip to content

Commit 5c2d113

Browse files
committed
feat(search): added experimental setup support for OpenSearch
1 parent 6485649 commit 5c2d113

File tree

8 files changed

+146
-22
lines changed

8 files changed

+146
-22
lines changed

docs/administration.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,10 +450,18 @@ You can click through the update wizard:
450450
### 5.6.5 Elasticsearch configuration
451451

452452
Here you can create and drop the Elasticsearch index, and you can run a full import of all data from your database
453-
into the Elasticsearch index. You can also see some Elasticsearch relevant usage data. This page is only available if
454-
Elasticsearch is enabled.
453+
into the Elasticsearch index.
454+
You can also see some Elasticsearch relevant usage data.
455+
This page is only available if Elasticsearch is enabled.
455456

456-
### 5.6.6 System information
457+
### 5.6.6 OpenSearch configuration
458+
459+
Here you can create and drop the OpenSearch index, and you can run a full import of all data from your database
460+
into the OpenSearch index.
461+
You can also see some OpenSearch relevant usage data.
462+
This page is only available if OpenSearch is enabled.
463+
464+
### 5.6.7 System information
457465

458466
On this page, phpMyFAQ displays some relevant system information like PHP version, database version or session path.
459467
Please use this information when reporting bugs.

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
phpMyFAQ is a comprehensive, multilingual FAQ system that is entirely database-driven.
44
It is compatible with a variety of databases for data storage and requires PHP 8.2+ for data access.
55
The system features a multi-language Content Management System equipped with a WYSIWYG editor and an Image Manager.
6-
It also provides real-time search capabilities with Elasticsearch.
6+
It also provides real-time search capabilities with Elasticsearch or OpenSearch.
77

88
phpMyFAQ supports flexible multi-user functionality,
99
offering user and group-based permissions on categories and records.

docs/installation.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,10 @@ of SQLite.
185185
If PHP was compiled with the LDAP extension, you can add your LDAP or Microsoft Active Directory information, too.
186186
Then you can insert your LDAP or Microsoft Active Directory information as well.
187187

188-
### Step 3: Elasticsearch support
188+
### Step 3: Elasticsearch and OpenSearch support
189189

190-
If you want to use Elasticsearch, you can activate this in the third step. You have to add at least one Elasticsearch
191-
node and the index name.
190+
If you want to use Elasticsearch or OpenSearch, you can activate this in the third step.
191+
You have to add at least one Elasticsearch or OpenSearch node and the index name.
192192

193193
### Step 4: Admin user setup
194194

@@ -231,7 +231,7 @@ You can change
231231
The term you are looking for should also not be in more than 50% of all your entries, or it will automatically be
232232
excluded from search. This is not a bug, but rather a feature of MySQL.
233233
- The search on other databases is using the LIKE operator currently.
234-
- To improve the search functionality, you should consider using Elasticsearch.
234+
- To improve the search functionality, you should consider using Elasticsearch or OpenSearch.
235235

236236
## 2.9 Automatic user language detection
237237

@@ -353,7 +353,17 @@ If you choose to add this during installation, the file will be automatically wr
353353
If you enabled Elasticsearch support in the admin configuration panel, you can create, re-import and delete your
354354
index with a user-friendly interface.
355355

356-
## 2.17 SSO (Single Sign-On) Support
356+
## 2.17 OpenSearch Support
357+
358+
To improve the search performance and quality of search results, it's possible to use OpenSearch.
359+
You need a running OpenSearch instance accessible by phpMyFAQ via HTTP/REST.
360+
You can add the IP(s)/Domain(s) and port(s) of your OpenSearch cluster during installation or later by renaming the
361+
OpenSearch file located in the folder config/.
362+
If you choose to add this during installation, the file will be automatically written and the index will be built.
363+
If you enabled OpenSearch support in the admin configuration panel, you can create, re-import and delete your
364+
index with a user-friendly interface.
365+
366+
## 2.18 SSO (Single Sign-On) Support
357367

358368
phpMyFAQ supports SSO (Single Sign-On)
359369
with the REMOTE_USER server variable is populated by the web server or application server

docs/thank-you.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ Thank you for using phpMyFAQ! :-)
44

55
Authors: Thorsten Rinne, Stephan Hochhaus, Markus Gläser, Jan Harms
66

7-
© 2001-2024 phpMyFAQ Team
7+
© 2001-2025 phpMyFAQ Team
88

99
This documentation is licensed under a [Creative Commons License](http://creativecommons.org/licenses/by/2.0/).

phpmyfaq/assets/src/configuration/setup.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const selectDatabaseSetup = (event: Event): void => {
2424

2525
const target = event.target as HTMLSelectElement;
2626

27-
if (target.value === 'sqlite3') {
27+
if (target.value === 'sqlite3' || target.value === 'pdo_sqlite') {
2828
for (let i: number = 0; i < inputs.length; i++) {
2929
inputs[i].removeAttribute('required');
3030
}
@@ -38,21 +38,25 @@ export const selectDatabaseSetup = (event: Event): void => {
3838

3939
switch (target.value) {
4040
case 'mysqli':
41+
case 'pdo_mysql':
4142
databasePort.value = '3306';
4243
sqlite.className = 'd-none';
4344
database.className = 'd-block';
4445
break;
4546
case 'pgsql':
47+
case 'pdo_pgsql':
4648
databasePort.value = '5432';
4749
sqlite.className = 'd-none';
4850
database.className = 'd-block';
4951
break;
5052
case 'sqlsrv':
53+
case 'pdo_sqlsrv':
5154
databasePort.value = '1433';
5255
sqlite.className = 'd-none';
5356
database.className = 'd-block';
5457
break;
5558
case 'sqlite3':
59+
case 'pdo_sqlite':
5660
sqlite.className = 'd-block';
5761
database.className = 'd-none';
5862
break;

phpmyfaq/assets/templates/setup/index.twig

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,15 +196,28 @@
196196
</div>
197197

198198
<div class="step">
199-
<h3 class="mb-3"> Step 3/4: Elasticsearch setup</h3>
199+
<h3 class="mb-3"> Step 3/4: Elasticsearch / OpenSearch setup</h3>
200200
{% if isElasticsearchEnabled %}
201-
<div class="form-group">
202-
<div class="form-check">
203-
<input id="elasticsearch_enabled" class="form-check-input" type="checkbox"
204-
name="elasticsearch_enabled" value="yes">
205-
<label class="form-check-label" for="elasticsearch_enabled">
206-
Enable Elasticsearch support?
207-
</label>
201+
<div class="row mb-2">
202+
<div class="form-group">
203+
<div class="form-check">
204+
<input id="elasticsearch_enabled" class="form-check-input" type="checkbox"
205+
name="elasticsearch_enabled" value="yes">
206+
<label class="form-check-label" for="elasticsearch_enabled">
207+
Enable Elasticsearch support?
208+
</label>
209+
</div>
210+
</div>
211+
</div>
212+
<div class="row mb-2">
213+
<div class="form-group">
214+
<div class="form-check">
215+
<input id="opensearch_enabled" class="form-check-input" type="checkbox"
216+
name="opensearch_enabled" value="yes">
217+
<label class="form-check-label" for="opensearch_enabled">
218+
Enable OpenSearch support?
219+
</label>
220+
</div>
208221
</div>
209222
</div>
210223
<div class="row mb-2">
@@ -214,11 +227,12 @@
214227
<input type="text" name="elasticsearch_server[]" id="elasticsearch_server"
215228
class="form-control" placeholder="127.0.0.1:9200">
216229
<span class="input-group-text" id="pmf-add-elasticsearch-host" style="cursor: pointer;">
217-
Add another Elasticsearch Host
230+
Add another host
218231
</span>
219232
</div>
220233
<small class="form-text text-muted">
221-
Please enter the host (domain or IP) with port number of your Elasticsearch server.
234+
Please enter the host (domain or IP) with port number of your Elasticsearch/OpenSearch server.
235+
The format for OpenSearch is <code>https://<em>domain</em>:<em>port</em></code>.
222236
</small>
223237
</div>
224238
</div>

phpmyfaq/src/phpMyFAQ/Instance/Setup.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,21 @@ public function createElasticsearchFile(array $data, string $folder = '/content/
168168
LOCK_EX
169169
);
170170
}
171+
172+
/**
173+
* Creates the file /content/core/config/opensearch.php
174+
*
175+
* @param int[]|string[] $data Array with OpenSearch credentials
176+
* @param string $folder Folder
177+
*/
178+
public function createOpenSearchFile(array $data, string $folder = '/content/core/config'): int|bool
179+
{
180+
return file_put_contents(
181+
$this->rootDir . $folder . '/config/opensearch.php',
182+
'<?php
183+
$PMF_OS[\'hosts\'] = [\'' . implode("'], ['", $data['hosts']) . "'];\n" .
184+
"\$PMF_OS['index'] = '" . $data['index'] . "';\n",
185+
LOCK_EX
186+
);
187+
}
171188
}

phpmyfaq/src/phpMyFAQ/Setup/Installer.php

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
use Composer\Autoload\ClassLoader;
2121
use Elastic\Elasticsearch\ClientBuilder;
2222
use Elastic\Elasticsearch\Exception\AuthenticationException;
23+
use OpenSearch\SymfonyClientFactory;
2324
use phpMyFAQ\Configuration;
2425
use phpMyFAQ\Configuration\DatabaseConfiguration;
2526
use phpMyFAQ\Configuration\ElasticsearchConfiguration;
27+
use phpMyFAQ\Configuration\OpenSearchConfiguration;
2628
use phpMyFAQ\Core\Exception;
2729
use phpMyFAQ\Database;
2830
use phpMyFAQ\Database\DatabaseDriver;
@@ -36,6 +38,7 @@
3638
use phpMyFAQ\Instance\Database\Stopwords;
3739
use phpMyFAQ\Instance\Elasticsearch;
3840
use phpMyFAQ\Instance\Main;
41+
use phpMyFAQ\Instance\OpenSearch;
3942
use phpMyFAQ\Instance\Setup;
4043
use phpMyFAQ\Ldap;
4144
use phpMyFAQ\Link;
@@ -729,6 +732,7 @@ public function checkInitialRewriteBasePath(Request $request): bool
729732
*
730733
* @param array|null $setup
731734
* @throws Exception|AuthenticationException
735+
* @throws \Exception
732736
*/
733737
public function startInstall(array|null $setup = null): void
734738
{
@@ -907,7 +911,7 @@ public function startInstall(array|null $setup = null): void
907911
$classLoader->addPsr4('React\\Promise\\', PMF_SRC_DIR . '/libs/react/promise/src');
908912
$classLoader->register();
909913

910-
// check LDAP connection
914+
// check Elasticsearch connection
911915
$esHosts = array_values($esHosts['elasticsearch_server']);
912916
$esClient = ClientBuilder::create()->setHosts($esHosts)->build();
913917

@@ -918,6 +922,48 @@ public function startInstall(array|null $setup = null): void
918922
$esSetup = [];
919923
}
920924

925+
//
926+
// Check OpenSearch if enabled
927+
//
928+
$openSearchEnabled = Filter::filterInput(INPUT_POST, 'opensearch_enabled', FILTER_SANITIZE_SPECIAL_CHARS);
929+
if (!is_null($openSearchEnabled)) {
930+
$osSetup = [];
931+
$osHostFilter = [
932+
'opensearch_server' => [
933+
'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
934+
'flags' => FILTER_REQUIRE_ARRAY
935+
]
936+
];
937+
938+
// OS hosts
939+
$osHosts = Filter::filterInputArray(INPUT_POST, $osHostFilter);
940+
if (is_null($osHosts)) {
941+
throw new Exception('OpenSearch Installation Error: Please add at least one OpenSearch host.');
942+
}
943+
944+
$osSetup['hosts'] = $osHosts['opensearch_server'];
945+
946+
// OS Index name
947+
$osSetup['index'] = Filter::filterInput(INPUT_POST, 'opensearch_index', FILTER_SANITIZE_SPECIAL_CHARS);
948+
if (is_null($osSetup['index'])) {
949+
throw new Exception('OpenSearch Installation Error: Please add an OpenSearch index name.');
950+
}
951+
952+
// check OpenSearch connection
953+
$osHosts = array_values($osHosts['opensearch_server']);
954+
$osClient = (new SymfonyClientFactory())->create([
955+
'base_uri' => $osHosts[0],
956+
'verify_peer' => false,
957+
]);
958+
959+
if (!$osClient) {
960+
throw new Exception('OpenSearch Installation Error: No connection to OpenSearch.');
961+
}
962+
} else {
963+
$osSetup = [];
964+
}
965+
966+
921967
// check the login name
922968
if (!isset($setup['loginname'])) {
923969
$loginName = Filter::filterInput(INPUT_POST, 'loginname', FILTER_SANITIZE_SPECIAL_CHARS);
@@ -999,6 +1045,14 @@ public function startInstall(array|null $setup = null): void
9991045
);
10001046
}
10011047

1048+
// check if OpenSearch is enabled
1049+
if (!is_null($openSearchEnabled) && count($osSetup) && !$instanceSetup->createOpenSearchFile($osSetup, '')) {
1050+
self::cleanFailedInstallationFiles();
1051+
throw new Exception(
1052+
'OpenSearch Installation Error: Setup cannot write to ./content/core/config/opensearch.php.'
1053+
);
1054+
}
1055+
10021056
// connect to the database using config/database.php
10031057
$databaseConfiguration = new DatabaseConfiguration($rootDir . '/content/core/config/database.php');
10041058
try {
@@ -1134,6 +1188,23 @@ public function startInstall(array|null $setup = null): void
11341188
$esInstance->createIndex();
11351189
}
11361190

1191+
// connect to OpenSearch if enabled
1192+
if (!is_null($openSearchEnabled) && is_file($rootDir . '/config/opensearch.php')) {
1193+
$osConfiguration = new OpenSearchConfiguration($rootDir . '/config/opensearch.php');
1194+
1195+
$configuration->setOpenSearchConfig($osConfiguration);
1196+
1197+
$osClient = (new SymfonyClientFactory())->create([
1198+
'base_uri' => $osConfiguration->getHosts()[0],
1199+
'verify_peer' => false,
1200+
]);
1201+
1202+
$configuration->setOpenSearch($osClient);
1203+
1204+
$osInstance = new OpenSearch($configuration);
1205+
$osInstance->createIndex();
1206+
}
1207+
11371208
// adjust RewriteBase in .htaccess
11381209
$configurator = new EnvironmentConfigurator($configuration);
11391210
$configurator->adjustRewriteBaseHtaccess();

0 commit comments

Comments
 (0)