diff --git a/app/App/MetaController.php b/app/App/MetaController.php
index 1515b4f7e..a94334c58 100644
--- a/app/App/MetaController.php
+++ b/app/App/MetaController.php
@@ -64,4 +64,14 @@ class MetaController extends Controller
'jsLibData' => file_get_contents(base_path('dev/licensing/js-library-licenses.txt')),
]);
}
+
+ /**
+ * Show the view for /opensearch.xml.
+ */
+ public function opensearch()
+ {
+ return response()
+ ->view('misc.opensearch')
+ ->header('Content-Type', 'application/opensearchdescription+xml');
+ }
}
diff --git a/lang/en/common.php b/lang/en/common.php
index 266174eed..b05169bb2 100644
--- a/lang/en/common.php
+++ b/lang/en/common.php
@@ -107,4 +107,7 @@ return [
// Not directly used but available for convenience to users.
'privacy_policy' => 'Privacy Policy',
'terms_of_service' => 'Terms of Service',
+
+ // OpenSearch
+ 'opensearch_description' => 'Search :appName',
];
diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php
index 4d4d07dc2..ddecb8e76 100644
--- a/resources/views/layouts/base.blade.php
+++ b/resources/views/layouts/base.blade.php
@@ -32,6 +32,9 @@
+
+
+
@yield('head')
diff --git a/resources/views/misc/opensearch.blade.php b/resources/views/misc/opensearch.blade.php
new file mode 100644
index 000000000..5c1503fc4
--- /dev/null
+++ b/resources/views/misc/opensearch.blade.php
@@ -0,0 +1,12 @@
+
+
+ {{ mb_strimwidth(setting('app-name'), 0, 16) }}
+ {{ trans('common.opensearch_description', ['appName' => setting('app-name')]) }}
+ {{ setting('app-icon') ?: url('/icon.png') }}
+ {{ setting('app-icon-180') ?: url('/icon-180.png') }}
+ {{ setting('app-icon-128') ?: url('/icon-128.png') }}
+ {{ setting('app-icon-64') ?: url('/icon-64.png') }}
+ {{ setting('app-icon-32') ?: url('/icon-32.png') }}
+
+
+
diff --git a/routes/web.php b/routes/web.php
index 58b8f4e54..81b938f32 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -23,6 +23,7 @@ Route::get('/robots.txt', [MetaController::class, 'robots']);
Route::get('/favicon.ico', [MetaController::class, 'favicon']);
Route::get('/manifest.json', [MetaController::class, 'pwaManifest']);
Route::get('/licenses', [MetaController::class, 'licenses']);
+Route::get('/opensearch.xml', [MetaController::class, 'opensearch']);
// Authenticated routes...
Route::middleware('auth')->group(function () {
diff --git a/tests/HelpTest.php b/tests/Meta/HelpTest.php
similarity index 94%
rename from tests/HelpTest.php
rename to tests/Meta/HelpTest.php
index 9cf80717f..e1de96bc8 100644
--- a/tests/HelpTest.php
+++ b/tests/Meta/HelpTest.php
@@ -1,6 +1,8 @@
put('app-name', $appName);
+ $resultUrl = url('/search') . '?term={searchTerms}';
+ $selfUrl = url('/opensearch.xml');
+
+ $resp = $this->get('/opensearch.xml');
+ $resp->assertOk();
+
+ $html = $this->withHtml($resp);
+
+ $html->assertElementExists('OpenSearchDescription > ShortName');
+ $html->assertElementContains('OpenSearchDescription > ShortName', mb_strimwidth($appName, 0, 16));
+ $html->assertElementNotContains('OpenSearchDescription > ShortName', $appName);
+
+ $html->assertElementExists('OpenSearchDescription > Description');
+ $html->assertElementContains('OpenSearchDescription > Description', "Search {$appName}");
+ $html->assertElementExists('OpenSearchDescription > Image');
+ $html->assertElementExists('OpenSearchDescription > Url[rel="results"][template="' . htmlspecialchars($resultUrl) . '"]');
+ $html->assertElementExists('OpenSearchDescription > Url[rel="self"][template="' . htmlspecialchars($selfUrl) . '"]');
+ }
+
+ public function test_opensearch_linked_to_from_home()
+ {
+ $appName = setting('app-name');
+ $endpointUrl = url('/opensearch.xml');
+
+ $resp = $this->asViewer()->get('/');
+ $html = $this->withHtml($resp);
+
+ $html->assertElementExists('head > link[rel="search"][type="application/opensearchdescription+xml"][title="' . htmlspecialchars($appName) . '"][href="' . htmlspecialchars($endpointUrl) . '"]');
+ }
+}
diff --git a/tests/PwaManifestTest.php b/tests/Meta/PwaManifestTest.php
similarity index 98%
rename from tests/PwaManifestTest.php
rename to tests/Meta/PwaManifestTest.php
index c66f8b360..fc6d19b54 100644
--- a/tests/PwaManifestTest.php
+++ b/tests/Meta/PwaManifestTest.php
@@ -1,6 +1,8 @@
get('/robots.txt')->assertSee("User-agent: *\nDisallow: /");
+
+ $this->setSettings(['app-public' => 'true']);
+
+ $resp = $this->get('/robots.txt');
+ $resp->assertSee("User-agent: *\nDisallow:");
+ $resp->assertDontSee('Disallow: /');
+ }
+
+ public function test_robots_effected_by_setting()
+ {
+ $this->get('/robots.txt')->assertSee("User-agent: *\nDisallow: /");
+
+ config()->set('app.allow_robots', true);
+
+ $resp = $this->get('/robots.txt');
+ $resp->assertSee("User-agent: *\nDisallow:");
+ $resp->assertDontSee('Disallow: /');
+
+ // Check config overrides app-public setting
+ config()->set('app.allow_robots', false);
+ $this->setSettings(['app-public' => 'true']);
+ $this->get('/robots.txt')->assertSee("User-agent: *\nDisallow: /");
+ }
+}
diff --git a/tests/PublicActionTest.php b/tests/PublicActionTest.php
index 875b279a8..76745aaac 100644
--- a/tests/PublicActionTest.php
+++ b/tests/PublicActionTest.php
@@ -128,33 +128,6 @@ class PublicActionTest extends TestCase
$resp->assertDontSee($page->name);
}
- public function test_robots_effected_by_public_status()
- {
- $this->get('/robots.txt')->assertSee("User-agent: *\nDisallow: /");
-
- $this->setSettings(['app-public' => 'true']);
-
- $resp = $this->get('/robots.txt');
- $resp->assertSee("User-agent: *\nDisallow:");
- $resp->assertDontSee('Disallow: /');
- }
-
- public function test_robots_effected_by_setting()
- {
- $this->get('/robots.txt')->assertSee("User-agent: *\nDisallow: /");
-
- config()->set('app.allow_robots', true);
-
- $resp = $this->get('/robots.txt');
- $resp->assertSee("User-agent: *\nDisallow:");
- $resp->assertDontSee('Disallow: /');
-
- // Check config overrides app-public setting
- config()->set('app.allow_robots', false);
- $this->setSettings(['app-public' => 'true']);
- $this->get('/robots.txt')->assertSee("User-agent: *\nDisallow: /");
- }
-
public function test_default_favicon_file_created_upon_access()
{
$faviconPath = public_path('favicon.ico');