├── .craftplugin
├── .editorconfig
├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
├── resources
└── promo-banner.png
└── src
├── Algolia.php
├── config.php
├── controllers
└── DefaultController.php
├── icon-mask.svg
├── icon.svg
├── models
└── Settings.php
├── services
└── AlgoliaService.php
├── templates
└── settings.twig
└── variables
└── AlgoliaVariable.php
/.craftplugin:
--------------------------------------------------------------------------------
1 | {"pluginName":"Algolia","pluginDescription":"Easily pull search results from Algolia into your Craft CMS website","pluginVersion":"2.0.0","pluginAuthorName":"TrendyMinds","pluginVendorName":"trendyminds","pluginAuthorUrl":"https://trendyminds.com","pluginAuthorGithub":"trendyminds","codeComments":"","pluginComponents":["controllers","services","settings","variables"],"consolecommandName":"","controllerName":"","cpsectionName":"","elementName":"","fieldName":"","modelName":"","purchasableName":"","recordName":"","serviceName":"","taskName":"","utilityName":"","widgetName":"","apiVersion":"api_version_3_0"}
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = tab
8 | indent_size = 2
9 | trim_trailing_whitespace = true
10 |
11 | [*.php]
12 | indent_size = 4
13 |
14 | [*.md]
15 | trim_trailing_whitespace = false
16 |
17 | [*.{yml,yaml}]
18 | indent_size = 2
19 | indent_style = space
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # CRAFT ENVIRONMENT
2 | .env.php
3 | .env.sh
4 | .env
5 |
6 | # COMPOSER
7 | /vendor
8 |
9 | # BUILD FILES
10 | /bower_components/*
11 | /node_modules/*
12 | /build/*
13 | /yarn-error.log
14 |
15 | # MISC FILES
16 | .cache
17 | .DS_Store
18 | .idea
19 | .project
20 | .settings
21 | *.esproj
22 | *.sublime-workspace
23 | *.sublime-project
24 | *.tmproj
25 | *.tmproject
26 | .vscode/*
27 | !.vscode/settings.json
28 | !.vscode/tasks.json
29 | !.vscode/launch.json
30 | !.vscode/extensions.json
31 | config.codekit3
32 | prepros-6.config
33 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Release Notes for Algolia
2 |
3 | ## 4.0.1 - 2025-05-12
4 |
5 | ### Changed
6 | * Removed `composer.lock` from project
7 |
8 | ## 4.0.0 - 2022-07-05
9 |
10 | ### Added
11 | * Support for Craft 4
12 |
13 | ## 3.1.0 - 2022-02-11
14 |
15 | ### Changed
16 | * Algolia PHP SDK updated to 3.2
17 | * PHP 7.3+ required
18 | * Craft 3.5+ required
19 | * Swapped tightenco/collect with illuminate/collections
20 |
21 | ## 3.0.1 - 2021-06-29
22 |
23 | ### Fixed
24 | * Coerce filters with boolean values to a string value of `"true"` or `"false"` to cooperate with Algolia syntax engine
25 |
26 | ## 3.0.0 - 2021-06-29
27 |
28 | ### Added
29 | * Helper to convert an object of filters into the stringified Algolia syntax for filtered searches
30 |
31 | ### Changed
32 | * Algolia PHP SDK updated to 3.x
33 | * PHP 7.1+ required
34 | * Craft 3.1.19+ required
35 |
36 | ## 2.0.0 - 2019-03-15
37 |
38 | Initial Craft 3 release
39 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 TrendyMinds
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
Algolia
2 |
3 |
4 |
5 | ## About
6 |
7 | Algolia for Craft CMS allows you to easily pull search results from Algolia into your Twig templates or through REST API endpoints.
8 |
9 | ## Template Usage
10 |
11 | ### Browse an index
12 |
13 | [Additional search parameters](https://www.algolia.com/doc/api-reference/search-api-parameters/) can be provided in the `params` object.
14 |
15 | ```twig
16 | {% for result in craft.algolia.browse({
17 | index: "indexName",
18 | query: "optional query",
19 | params: {
20 | distinct: true,
21 | getRankingInfo: true
22 | }
23 | })
24 | %}
25 |
26 | {% endfor %}
27 | ```
28 |
29 | ### Search an index
30 |
31 | [Additional search parameters](https://www.algolia.com/doc/api-reference/search-api-parameters/) can be provided in the `params` object.
32 |
33 | ```twig
34 | {% set search = craft.algolia.search({
35 | index: "indexName",
36 | query: "optional query",
37 | params: {
38 | hitsPerPage: 5,
39 | page: 7
40 | }
41 | })
42 | %}
43 |
44 | {% for hit in search.hits %}
45 |
46 | {% endfor %}
47 | ```
48 |
49 | ### Perform a multiple query search
50 |
51 | [Additional search parameters](https://www.algolia.com/doc/api-reference/search-api-parameters/) can be provided in each `queries` object.
52 |
53 | ```twig
54 | {% set search = craft.algolia.multipleQueries([
55 | {
56 | indexName: "indexName1",
57 | query: "optional query"
58 | },
59 | {
60 | indexName: "indexName2",
61 | query: "optional query"
62 | }
63 | ])
64 | %}
65 |
66 | {% for group in search.results %}
67 | {% for hit in group.hits %}
68 |
69 | {% endfor %}
70 | {% endfor %}
71 | ```
72 |
73 | ### Parsing filters
74 |
75 | When using filters in an Algolia search the engine requires [a particular syntax](https://www.algolia.com/doc/api-reference/api-parameters/filters/). Rather than stringifying your filters in Twig this plugin offers a way to convert an object like...
76 |
77 | ```json
78 | {
79 | "food": ["fries", "cake", "pizza"],
80 | "colors": ["blue", "green", "red"],
81 | "featured": true
82 | }
83 | ```
84 | to a valid Algolia filter string like...
85 | ```
86 | '(food:"fries" OR food:"cake" OR food:"pizza") AND (colors:"blue" OR colors:"green" OR colors:"red") AND (featured:"1")'
87 | ```
88 |
89 | ```twig
90 | {% set filters = {
91 | food: ['fries', 'cake', 'pizza'],
92 | colors: ['blue', 'green', 'red'],
93 | featured: true
94 | } %}
95 |
96 | {{ craft.algolia.parseFilters(filters) }}
97 | ```
98 |
99 | ## Using JSON REST API controllers
100 | In additional to Twig variables you can make a `POST` request to one of the following controller endpoints. The same index, query and optional attributes are available when you make your `POST` request.
101 |
102 | **NOTE**: The following examples use [Axios](https://github.com/axios/axios) for making API requests
103 |
104 | ### Browse an index
105 |
106 | [Additional search parameters](https://www.algolia.com/doc/api-reference/search-api-parameters/) can be provided in the `params` object.
107 |
108 | ```js
109 | axios.post("/actions/algolia/default/browse", {
110 | index: "indexName",
111 | query: "optional query",
112 | params: {
113 | distinct: true,
114 | getRankingInfo: true
115 | }
116 | }, {
117 | headers: {
118 | "X-CSRF-Token": YOUR_CRAFT_CSRF_TOKEN
119 | }
120 | });
121 | ```
122 |
123 | ### Search an index
124 |
125 | [Additional search parameters](https://www.algolia.com/doc/api-reference/search-api-parameters/) can be provided in the `params` object.
126 |
127 | ```js
128 | axios.post("/actions/algolia/default/search", {
129 | index: "indexName",
130 | query: "optional query",
131 | params: {
132 | hitsPerPage: 5,
133 | page: 7
134 | }
135 | }, {
136 | headers: {
137 | "X-CSRF-Token": YOUR_CRAFT_CSRF_TOKEN
138 | }
139 | });
140 | ```
141 |
142 | ### Perform a multiple query search
143 |
144 | [Additional search parameters](https://www.algolia.com/doc/api-reference/search-api-parameters/) can be provided in each `queries` object.
145 |
146 | ```js
147 | axios.post("/actions/algolia/default/multiple-queries", {
148 | queries: [
149 | {
150 | indexName: "indexName1",
151 | query: "optional query"
152 | },
153 | {
154 | indexName: "indexName2",
155 | query: "optional query"
156 | }
157 | ]
158 | }, {
159 | headers: {
160 | "X-CSRF-Token": YOUR_CRAFT_CSRF_TOKEN
161 | }
162 | });
163 | ```
164 |
165 | ## Requirements
166 |
167 | This plugin requires Craft CMS 3.1.19+ and PHP 7.1+.
168 |
169 | ## Installation
170 |
171 | [Install Algolia from the Craft CMS Plugin Store!](https://plugins.craftcms.com/algolia)
172 |
173 | ## Attribution
174 | This plugin is powered by the [Algolia PHP API client](https://www.algolia.com/doc/api-client/getting-started/install/php/). The client and icon/logo belong to Algolia.
175 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "trendyminds/algolia",
3 | "description": "Easily pull search results from Algolia into your Craft CMS website",
4 | "type": "craft-plugin",
5 | "version": "4.0.1",
6 | "keywords": [
7 | "craft",
8 | "cms",
9 | "craftcms",
10 | "craft-plugin",
11 | "algolia"
12 | ],
13 | "support": {
14 | "docs": "https://github.com/trendyminds/algolia/blob/master/README.md",
15 | "issues": "https://github.com/trendyminds/algolia/issues"
16 | },
17 | "license": "MIT",
18 | "authors": [
19 | {
20 | "name": "TrendyMinds",
21 | "homepage": "https://trendyminds.com"
22 | }
23 | ],
24 | "require": {
25 | "php": "^8.0.2",
26 | "craftcms/cms": "^4.0.0",
27 | "algolia/algoliasearch-client-php": "^3.2"
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "trendyminds\\algolia\\": "src/"
32 | }
33 | },
34 | "extra": {
35 | "name": "Algolia",
36 | "handle": "algolia",
37 | "hasCpSettings": true,
38 | "hasCpSection": false,
39 | "changelogUrl": "https://raw.githubusercontent.com/trendyminds/algolia/master/CHANGELOG.md",
40 | "components": {
41 | "algoliaService": "trendyminds\\algolia\\services\\AlgoliaService"
42 | },
43 | "class": "trendyminds\\algolia\\Algolia"
44 | },
45 | "config": {
46 | "allow-plugins": {
47 | "yiisoft/yii2-composer": true,
48 | "craftcms/plugin-installer": true
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/resources/promo-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trendyminds/algolia/549b5c6cdd5aa35b2c06859baba76303c6e8b03a/resources/promo-banner.png
--------------------------------------------------------------------------------
/src/Algolia.php:
--------------------------------------------------------------------------------
1 | sender;
68 | $variable->set('algolia', AlgoliaVariable::class);
69 | }
70 | );
71 | }
72 |
73 | // Protected Methods
74 | // =========================================================================
75 |
76 | /**
77 | * @inheritdoc
78 | */
79 | protected function createSettingsModel(): ?Model
80 | {
81 | return new Settings();
82 | }
83 |
84 | /**
85 | * @inheritdoc
86 | */
87 | protected function settingsHtml(): string
88 | {
89 | return Craft::$app->view->renderTemplate(
90 | 'algolia/settings',
91 | [
92 | 'settings' => $this->getSettings()
93 | ]
94 | );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/config.php:
--------------------------------------------------------------------------------
1 | "",
27 | "apiKey" => "",
28 | ];
29 |
--------------------------------------------------------------------------------
/src/controllers/DefaultController.php:
--------------------------------------------------------------------------------
1 | requirePostRequest();
47 |
48 | $postData = Json::decode(Craft::$app->getRequest()->getRawBody(), true);
49 |
50 | $index = $postData["index"];
51 | $query = $postData["query"] ?? "";
52 | $searchParameters = $postData["params"] ?? [];
53 |
54 | $data = Algolia::$plugin->algoliaService->search($index, $query, $searchParameters);
55 |
56 | return $this->asJson($data);
57 | }
58 |
59 | /**
60 | * @return mixed
61 | */
62 | public function actionMultipleQueries(): Response
63 | {
64 | $this->requirePostRequest();
65 |
66 | $postData = Json::decode(Craft::$app->getRequest()->getRawBody(), true);
67 |
68 | $data = Algolia::$plugin->algoliaService->multipleQueries($postData['queries']);
69 |
70 | return $this->asJson($data);
71 | }
72 |
73 | /**
74 | * @return mixed
75 | */
76 | public function actionBrowse(): Response
77 | {
78 | $this->requirePostRequest();
79 |
80 | $postData = Json::decode(Craft::$app->getRequest()->getRawBody(), true);
81 |
82 | $index = $postData["index"];
83 | $query = $postData["query"] ?? "";
84 | $browseParameters = $postData["params"] ?? [];
85 |
86 | $data = Algolia::$plugin->algoliaService->browse($index, $query, $browseParameters);
87 |
88 | return $this->asJson($data);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/icon-mask.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/models/Settings.php:
--------------------------------------------------------------------------------
1 | ''],
45 |
46 | ['apiKey', 'string'],
47 | ['apiKey', 'default', 'value' => ''],
48 | ];
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/services/AlgoliaService.php:
--------------------------------------------------------------------------------
1 | getSettings()->applicationId);
34 | $apiKey = App::parseEnv(Algolia::$plugin->getSettings()->apiKey);
35 |
36 | $this->client = SearchClient::create($applicationId, $apiKey);
37 | }
38 |
39 | /**
40 | * Browse an index
41 | *
42 | * @param string $index
43 | * @param mixed $query
44 | * @param array $browseParameters
45 | * @return void
46 | */
47 | public function browse(string $index, $query = "", array $browseParameters = [])
48 | {
49 | $index = $this->client->initIndex($index);
50 |
51 | $requestOptions = [
52 | "query" => $query
53 | ];
54 |
55 | $requestOptions = array_merge($requestOptions, $browseParameters);
56 |
57 | $res = $index->browseObjects($requestOptions);
58 |
59 | return $res;
60 | }
61 |
62 | /**
63 | * Search within an index
64 | *
65 | * @param string $index
66 | * @param mixed $query
67 | * @param array $searchParameters
68 | * @return void
69 | */
70 | public function search(string $index, $query = "", array $searchParameters = [])
71 | {
72 | $index = $this->client->initIndex($index);
73 |
74 | $res = $index->search($query, $searchParameters);
75 |
76 | return $res;
77 | }
78 |
79 | /**
80 | * Perform a multiple query search
81 | *
82 | * @param array $queries
83 | * @return void
84 | */
85 | public function multipleQueries(array $queries = [])
86 | {
87 | $res = $this->client->multipleQueries($queries);
88 |
89 | return $res;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/templates/settings.twig:
--------------------------------------------------------------------------------
1 | {# @var craft \craft\web\twig\variables\CraftVariable #}
2 | {#
3 | /**
4 | * Algolia plugin for Craft CMS 4.x
5 | *
6 | * Algolia Settings.twig
7 | *
8 | * @author TrendyMinds
9 | * @copyright Copyright (c) 2019 TrendyMinds
10 | * @link https://trendyminds.com
11 | * @package Algolia
12 | * @since 2.0.0
13 | */
14 | #}
15 |
16 | {% import "_includes/forms" as forms %}
17 |
18 | {{forms.autosuggestField({
19 | first: true,
20 | instructions: 'Your Application ID found in the "API keys" section of your Algolia account',
21 | label: 'Application ID',
22 | id: 'applicationId',
23 | name: 'applicationId',
24 | value: settings['applicationId'],
25 | suggestEnvVars: true
26 | })
27 | }}
28 |
29 | {{forms.autosuggestField({
30 | instructions: 'Your Search-Only API key found in the "API keys" section of your Algolia account',
31 | label: 'API Key',
32 | id: 'apiKey',
33 | name: 'apiKey',
34 | value: settings['apiKey'],
35 | suggestEnvVars: true
36 | })
37 | }}
38 |
--------------------------------------------------------------------------------
/src/variables/AlgoliaVariable.php:
--------------------------------------------------------------------------------
1 | index;
31 | $query = $options->query ?? "";
32 | $browseParameters = $options->params ?? [];
33 |
34 | return Algolia::$plugin->algoliaService->browse($index, $query, $browseParameters);
35 | }
36 |
37 | public function search(array $options = [])
38 | {
39 | $options = (object) $options;
40 |
41 | $index = $options->index;
42 | $query = $options->query ?? "";
43 | $searchParameters = $options->params ?? [];
44 |
45 | return Algolia::$plugin->algoliaService->search($index, $query, $searchParameters);
46 | }
47 |
48 | public function multipleQueries(array $queries = [])
49 | {
50 | return Algolia::$plugin->algoliaService->multipleQueries($queries);
51 | }
52 |
53 | /**
54 | * Parses an array of key/value filters into a string for the Algolia filtering engine
55 | *
56 | * @param array $filters A key/value pair of filters
57 | *
58 | * @return string A stringified version of your filters for Algolia to use in the search query
59 | */
60 | public function parseFilters(array $filters = []): string
61 | {
62 | return collect($filters)
63 | ->filter(function ($item) {
64 | // Remove filters that are either "null" or empty
65 | return gettype($item) !== 'NULL' && $item !== '';
66 | })->map(function ($items) {
67 | // If we have a boolean value we need to coerce it to a string of 'true' or 'false'. Otherwise Algolia will not understand the syntax
68 | return collect($items)->map(function ($item) {
69 | if (gettype($item) === 'boolean') {
70 | return $item === true ? 'true' : 'false';
71 | }
72 |
73 | return $item;
74 | });
75 | })->map(function ($item, $group) {
76 | // Convert each group of results into string-based "OR" searches for Algolia's parsing engine
77 | return "($group:\"" . collect($item)->join("\" OR $group:\"") . "\")";
78 | })
79 | ->join(" AND "); // Combine all terms together
80 | }
81 | }
82 |
--------------------------------------------------------------------------------