├── .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 logo Algolia 2 | 3 | Query your Algolia data using Twig 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 | --------------------------------------------------------------------------------