├── .codeclimate.yml ├── .eslintignore ├── .eslintrc ├── .travis.yml ├── Block └── Page │ ├── Result.php │ └── Suggest.php ├── Controller └── Result │ └── Index.php ├── Helper └── Configuration.php ├── ISSUE_TEMPLATE.md ├── Model ├── Autocomplete │ └── Page │ │ └── DataProvider.php ├── Page │ └── Indexer │ │ ├── Fulltext.php │ │ └── Fulltext │ │ └── Action │ │ └── Full.php └── ResourceModel │ └── Page │ ├── Fulltext │ └── Collection.php │ └── Indexer │ └── Fulltext │ └── Action │ └── Full.php ├── Plugin └── Indexer │ └── Page │ └── Save │ └── ReindexPageAfterSave.php ├── README.md ├── composer.json ├── etc ├── adminhtml │ └── system.xml ├── config.xml ├── db_schema.xml ├── db_schema_whitelist.json ├── di.xml ├── elasticsuite_indices.xml ├── elasticsuite_search_request.xml ├── frontend │ ├── di.xml │ └── routes.xml ├── indexer.xml ├── module.xml └── mview.xml ├── i18n ├── de_AT.csv ├── de_DE.csv └── fr_FR.csv ├── registration.php └── view ├── adminhtml └── ui_component │ ├── cms_page_form.xml │ └── cms_page_listing.xml └── frontend ├── layout ├── catalogsearch_result_index.xml ├── default.xml └── smile_elasticsuite_cms_result_index.xml ├── templates ├── cms_page_result.phtml └── cms_page_suggest.phtml └── web └── template └── autocomplete └── cms.html /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | duplication: 4 | enabled: true 5 | config: 6 | languages: 7 | - javascript 8 | - php 9 | eslint: 10 | enabled: true 11 | fixme: 12 | enabled: true 13 | phan: 14 | enabled: true 15 | config: 16 | file_extensions: php 17 | ignore-undeclared: true 18 | ratings: 19 | paths: 20 | - "**.js" 21 | - "**.php" 22 | exclude_paths: 23 | - src/*/Test 24 | - vendor/* 25 | - Resources/* 26 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*{.,-}min.js 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | ecmaFeatures: 2 | modules: true 3 | jsx: true 4 | 5 | env: 6 | amd: true 7 | browser: true 8 | es6: true 9 | jquery: true 10 | node: true 11 | 12 | # http://eslint.org/docs/rules/ 13 | rules: 14 | # Possible Errors 15 | comma-dangle: [2, never] 16 | no-cond-assign: 2 17 | no-console: 0 18 | no-constant-condition: 2 19 | no-control-regex: 2 20 | no-debugger: 2 21 | no-dupe-args: 2 22 | no-dupe-keys: 2 23 | no-duplicate-case: 2 24 | no-empty: 2 25 | no-empty-character-class: 2 26 | no-ex-assign: 2 27 | no-extra-boolean-cast: 2 28 | no-extra-parens: 0 29 | no-extra-semi: 2 30 | no-func-assign: 2 31 | no-inner-declarations: [2, functions] 32 | no-invalid-regexp: 2 33 | no-irregular-whitespace: 2 34 | no-negated-in-lhs: 2 35 | no-obj-calls: 2 36 | no-regex-spaces: 2 37 | no-sparse-arrays: 2 38 | no-unexpected-multiline: 2 39 | no-unreachable: 2 40 | use-isnan: 2 41 | valid-jsdoc: 0 42 | valid-typeof: 2 43 | 44 | # Best Practices 45 | accessor-pairs: 2 46 | block-scoped-var: 0 47 | complexity: [2, 6] 48 | consistent-return: 0 49 | curly: 0 50 | default-case: 0 51 | dot-location: 0 52 | dot-notation: 0 53 | eqeqeq: 2 54 | guard-for-in: 2 55 | no-alert: 2 56 | no-caller: 2 57 | no-case-declarations: 2 58 | no-div-regex: 2 59 | no-else-return: 0 60 | no-empty-label: 2 61 | no-empty-pattern: 2 62 | no-eq-null: 2 63 | no-eval: 2 64 | no-extend-native: 2 65 | no-extra-bind: 2 66 | no-fallthrough: 2 67 | no-floating-decimal: 0 68 | no-implicit-coercion: 0 69 | no-implied-eval: 2 70 | no-invalid-this: 0 71 | no-iterator: 2 72 | no-labels: 0 73 | no-lone-blocks: 2 74 | no-loop-func: 2 75 | no-magic-number: 0 76 | no-multi-spaces: 0 77 | no-multi-str: 0 78 | no-native-reassign: 2 79 | no-new-func: 2 80 | no-new-wrappers: 2 81 | no-new: 2 82 | no-octal-escape: 2 83 | no-octal: 2 84 | no-proto: 2 85 | no-redeclare: 2 86 | no-return-assign: 2 87 | no-script-url: 2 88 | no-self-compare: 2 89 | no-sequences: 0 90 | no-throw-literal: 0 91 | no-unused-expressions: 2 92 | no-useless-call: 2 93 | no-useless-concat: 2 94 | no-void: 2 95 | no-warning-comments: 0 96 | no-with: 2 97 | radix: 2 98 | vars-on-top: 0 99 | wrap-iife: 2 100 | yoda: 0 101 | 102 | # Strict 103 | strict: 0 104 | 105 | # Variables 106 | init-declarations: 0 107 | no-catch-shadow: 2 108 | no-delete-var: 2 109 | no-label-var: 2 110 | no-shadow-restricted-names: 2 111 | no-shadow: 0 112 | no-undef-init: 2 113 | no-undef: 0 114 | no-undefined: 0 115 | no-unused-vars: 0 116 | no-use-before-define: 0 117 | 118 | # Node.js and CommonJS 119 | callback-return: 2 120 | global-require: 2 121 | handle-callback-err: 2 122 | no-mixed-requires: 0 123 | no-new-require: 0 124 | no-path-concat: 2 125 | no-process-exit: 2 126 | no-restricted-modules: 0 127 | no-sync: 0 128 | 129 | # Stylistic Issues 130 | array-bracket-spacing: 0 131 | block-spacing: 0 132 | brace-style: 0 133 | camelcase: 0 134 | comma-spacing: 0 135 | comma-style: 0 136 | computed-property-spacing: 0 137 | consistent-this: 0 138 | eol-last: 0 139 | func-names: 0 140 | func-style: 0 141 | id-length: 0 142 | id-match: 0 143 | indent: 0 144 | jsx-quotes: 0 145 | key-spacing: 0 146 | linebreak-style: 0 147 | lines-around-comment: 0 148 | max-depth: 0 149 | max-len: 0 150 | max-nested-callbacks: 0 151 | max-params: 0 152 | max-statements: [2, 30] 153 | new-cap: 0 154 | new-parens: 0 155 | newline-after-var: 0 156 | no-array-constructor: 0 157 | no-bitwise: 0 158 | no-continue: 0 159 | no-inline-comments: 0 160 | no-lonely-if: 0 161 | no-mixed-spaces-and-tabs: 0 162 | no-multiple-empty-lines: 0 163 | no-negated-condition: 0 164 | no-nested-ternary: 0 165 | no-new-object: 0 166 | no-plusplus: 0 167 | no-restricted-syntax: 0 168 | no-spaced-func: 0 169 | no-ternary: 0 170 | no-trailing-spaces: 0 171 | no-underscore-dangle: 0 172 | no-unneeded-ternary: 0 173 | object-curly-spacing: 0 174 | one-var: 0 175 | operator-assignment: 0 176 | operator-linebreak: 0 177 | padded-blocks: 0 178 | quote-props: 0 179 | quotes: 0 180 | require-jsdoc: 0 181 | semi-spacing: 0 182 | semi: 0 183 | sort-vars: 0 184 | space-after-keywords: 0 185 | space-before-blocks: 0 186 | space-before-function-paren: 0 187 | space-before-keywords: 0 188 | space-in-parens: 0 189 | space-infix-ops: 0 190 | space-return-throw-case: 0 191 | space-unary-ops: 0 192 | spaced-comment: 0 193 | wrap-regex: 0 194 | 195 | # ECMAScript 6 196 | arrow-body-style: 0 197 | arrow-parens: 0 198 | arrow-spacing: 0 199 | constructor-super: 0 200 | generator-star-spacing: 0 201 | no-arrow-condition: 0 202 | no-class-assign: 0 203 | no-const-assign: 0 204 | no-dupe-class-members: 0 205 | no-this-before-super: 0 206 | no-var: 0 207 | object-shorthand: 0 208 | prefer-arrow-callback: 0 209 | prefer-const: 0 210 | prefer-reflect: 0 211 | prefer-spread: 0 212 | prefer-template: 0 213 | require-yield: 0 214 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - '7.0' 5 | - '7.1' 6 | 7 | install: [ 8 | "mkdir -p app/etc var", 9 | "echo \"{\\\"http-basic\\\":{\\\"repo.magento.com\\\":{\\\"username\\\":\\\"${MAGENTO_USERNAME}\\\",\\\"password\\\":\\\"${MAGENTO_PASSWORD}\\\"}}}\" > auth.json", 10 | "composer install --prefer-dist" 11 | ] 12 | 13 | cache: 14 | directories: 15 | - $HOME/.composer/cache 16 | 17 | script: 18 | - vendor/bin/phpcs --ignore=/vendor/,/app/ --standard=vendor/smile/magento2-smilelab-phpcs/phpcs-standards/SmileLab --extensions=php ./ 19 | - vendor/bin/phpmd ./ text vendor/smile/magento2-smilelab-phpmd/phpmd-rulesets/rulset.xml --exclude vendor 20 | -------------------------------------------------------------------------------- /Block/Page/Result.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2016 Smile 11 | * @license Open Software License ("OSL") v. 3.0 12 | */ 13 | namespace Smile\ElasticsuiteCms\Block\Page; 14 | 15 | use Magento\Framework\View\Element\Template\Context as TemplateContext; 16 | use Magento\Search\Model\QueryFactory; 17 | use Smile\ElasticsuiteCms\Model\ResourceModel\Page\Fulltext\CollectionFactory as PageCollectionFactory; 18 | 19 | /** 20 | * Plugin that happend custom fields dedicated to search configuration 21 | * 22 | * @SuppressWarnings(PHPMD.CouplingBetweenObjects) 23 | * 24 | * @category Smile 25 | * @package Smile\ElasticsuiteCms 26 | * @author Fanny DECLERCK 27 | */ 28 | class Result extends \Magento\Framework\View\Element\Template 29 | { 30 | /** 31 | * @var QueryFactory 32 | */ 33 | private $queryFactory; 34 | 35 | /** 36 | * @var \Smile\ElasticsuiteCms\Model\ResourceModel\Page\Fulltext\Collection 37 | */ 38 | private $pageCollection; 39 | 40 | /** 41 | * @var \Magento\Cms\Helper\Page 42 | */ 43 | protected $cmsPage; 44 | 45 | /** 46 | * Suggest constructor. 47 | * 48 | * @param TemplateContext $context Template contexte. 49 | * @param QueryFactory $queryFactory Query factory. 50 | * @param PageCollectionFactory $pageCollectionFactory Page collection factory. 51 | * @param \Magento\Cms\Helper\Page $cmsPage Cms helper page. 52 | * @param array $data Data. 53 | */ 54 | public function __construct( 55 | TemplateContext $context, 56 | QueryFactory $queryFactory, 57 | PageCollectionFactory $pageCollectionFactory, 58 | \Magento\Cms\Helper\Page $cmsPage, 59 | array $data = [] 60 | ) { 61 | parent::__construct($context, $data); 62 | 63 | $this->queryFactory = $queryFactory; 64 | $this->pageCollection = $this->initPageCollection($pageCollectionFactory); 65 | $this->cmsPage = $cmsPage; 66 | } 67 | 68 | /** 69 | * Prepare layout 70 | * 71 | * @return $this 72 | */ 73 | protected function _prepareLayout() 74 | { 75 | $title = $this->getSearchQueryText(); 76 | $this->pageConfig->getTitle()->set($title); 77 | // add Home breadcrumb 78 | $breadcrumbs = $this->getLayout()->getBlock('breadcrumbs'); 79 | if ($breadcrumbs) { 80 | $breadcrumbs->addCrumb( 81 | 'home', 82 | [ 83 | 'label' => __('Home'), 84 | 'title' => __('Go to Home Page'), 85 | 'link' => $this->_storeManager->getStore()->getBaseUrl() 86 | ] 87 | )->addCrumb( 88 | 'search', 89 | ['label' => $title, 'title' => $title] 90 | ); 91 | } 92 | 93 | return parent::_prepareLayout(); 94 | } 95 | 96 | /** 97 | * Returns cms page collection. 98 | * 99 | * @return \Smile\ElasticsuiteCms\Model\ResourceModel\Page\Fulltext\Collection 100 | */ 101 | public function getPageCollection() 102 | { 103 | return $this->pageCollection; 104 | } 105 | 106 | /** 107 | * Returns collection size. 108 | * 109 | * @return int|null 110 | */ 111 | public function getResultCount() 112 | { 113 | return $this->getPageCollection()->getSize(); 114 | } 115 | 116 | /** 117 | * Returns query. 118 | * 119 | * @return \Magento\Search\Model\Query 120 | */ 121 | public function getQuery() 122 | { 123 | return $this->queryFactory->get(); 124 | } 125 | 126 | /** 127 | * Returns query text. 128 | * 129 | * @return string 130 | */ 131 | public function getQueryText() 132 | { 133 | return $this->getQuery()->getQueryText(); 134 | } 135 | 136 | /** 137 | * Returns page url. 138 | * 139 | * @param int $pageId Page id 140 | * 141 | * @return mixed 142 | */ 143 | public function getPageUrl($pageId) 144 | { 145 | return $this->cmsPage->getPageUrl($pageId); 146 | } 147 | 148 | /** 149 | * Get search query text 150 | * 151 | * @return \Magento\Framework\Phrase 152 | */ 153 | public function getSearchQueryText() 154 | { 155 | return __("Search results for: '%1'", $this->escapeHtml($this->getQueryText())); 156 | } 157 | 158 | /** 159 | * Init cms page collection. 160 | * 161 | * @param PageCollectionFactory $collectionFactory Cms page collection. 162 | * 163 | * @return mixed 164 | */ 165 | private function initPageCollection($collectionFactory) 166 | { 167 | $pageCollection = $collectionFactory->create(); 168 | 169 | $queryText = $this->getQueryText(); 170 | $pageCollection->addSearchFilter($queryText); 171 | 172 | return $pageCollection; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Block/Page/Suggest.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2016 Smile 11 | * @license Open Software License ("OSL") v. 3.0 12 | */ 13 | namespace Smile\ElasticsuiteCms\Block\Page; 14 | 15 | use Magento\Framework\View\Element\Template\Context as TemplateContext; 16 | use Magento\Search\Model\QueryFactory; 17 | use Smile\ElasticsuiteCms\Model\ResourceModel\Page\Fulltext\CollectionFactory as PageCollectionFactory; 18 | use Smile\ElasticsuiteCms\Helper\Configuration; 19 | 20 | /** 21 | * Plugin that happend custom fields dedicated to search configuration 22 | * 23 | * @SuppressWarnings(PHPMD.CouplingBetweenObjects) 24 | * 25 | * @category Smile 26 | * @package Smile\ElasticsuiteCms 27 | * @author Fanny DECLERCK 28 | */ 29 | class Suggest extends \Magento\Framework\View\Element\Template 30 | { 31 | /** 32 | * Name of field to get max results. 33 | * 34 | * @var string 35 | */ 36 | const MAX_RESULT = 'max_result'; 37 | 38 | /** 39 | * @var QueryFactory 40 | */ 41 | private $queryFactory; 42 | 43 | /** 44 | * @var Configuration 45 | */ 46 | private $helper; 47 | 48 | /** 49 | * @var \Smile\ElasticsuiteCms\Model\ResourceModel\Page\Fulltext\Collection 50 | */ 51 | private $pageCollection; 52 | 53 | /** 54 | * @var \Magento\Cms\Helper\Page 55 | */ 56 | protected $cmsPage; 57 | 58 | /** 59 | * Suggest constructor. 60 | * 61 | * @param TemplateContext $context Template contexte. 62 | * @param QueryFactory $queryFactory Query factory. 63 | * @param PageCollectionFactory $pageCollectionFactory Page collection factory. 64 | * @param Configuration $helper Configuration helper. 65 | * @param \Magento\Cms\Helper\Page $cmsPage Cms helper page. 66 | * @param array $data Data. 67 | */ 68 | public function __construct( 69 | TemplateContext $context, 70 | QueryFactory $queryFactory, 71 | PageCollectionFactory $pageCollectionFactory, 72 | Configuration $helper, 73 | \Magento\Cms\Helper\Page $cmsPage, 74 | array $data = [] 75 | ) { 76 | parent::__construct($context, $data); 77 | 78 | $this->queryFactory = $queryFactory; 79 | $this->helper = $helper; 80 | $this->pageCollection = $this->initPageCollection($pageCollectionFactory); 81 | $this->cmsPage = $cmsPage; 82 | } 83 | 84 | /** 85 | * Returns if block can be display. 86 | * 87 | * @return bool 88 | */ 89 | public function canShowBlock() 90 | { 91 | return $this->getResultCount() > 0; 92 | } 93 | 94 | /** 95 | * Returns cms page collection. 96 | * 97 | * @return \Smile\ElasticsuiteCms\Model\ResourceModel\Page\Fulltext\Collection 98 | */ 99 | public function getPageCollection() 100 | { 101 | return $this->pageCollection; 102 | } 103 | 104 | /** 105 | * Returns number of results. 106 | * 107 | * @return int 108 | */ 109 | public function getNumberOfResults() 110 | { 111 | return $this->helper->getConfigValue(self::MAX_RESULT); 112 | } 113 | 114 | /** 115 | * Returns collection size. 116 | * 117 | * @return int|null 118 | */ 119 | public function getResultCount() 120 | { 121 | return $this->getPageCollection()->getSize(); 122 | } 123 | 124 | /** 125 | * Returns query. 126 | * 127 | * @return \Magento\Search\Model\Query 128 | */ 129 | public function getQuery() 130 | { 131 | return $this->queryFactory->get(); 132 | } 133 | 134 | /** 135 | * Returns query text. 136 | * 137 | * @return string 138 | */ 139 | public function getQueryText() 140 | { 141 | return $this->getQuery()->getQueryText(); 142 | } 143 | 144 | /** 145 | * Returns all results url page. 146 | * 147 | * @return string 148 | */ 149 | public function getShowAllUrl() 150 | { 151 | return $this->getUrl('elasticsuite_cms/result', ['q' => $this->getQueryText()]); 152 | } 153 | 154 | /** 155 | * Returns page url. 156 | * 157 | * @param int $pageId Page id 158 | * 159 | * @return mixed 160 | */ 161 | public function getPageUrl($pageId) 162 | { 163 | return $this->cmsPage->getPageUrl($pageId); 164 | } 165 | 166 | /** 167 | * Init cms page collection. 168 | * 169 | * @param PageCollectionFactory $collectionFactory Cms page collection. 170 | * 171 | * @return mixed 172 | */ 173 | private function initPageCollection($collectionFactory) 174 | { 175 | $pageCollection = $collectionFactory->create(); 176 | 177 | $pageCollection->setPageSize($this->getNumberOfResults()); 178 | 179 | $queryText = $this->getQueryText(); 180 | $pageCollection->addSearchFilter($queryText); 181 | 182 | return $pageCollection; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /Controller/Result/Index.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2016 Smile 11 | * @license Open Software License ("OSL") v. 3.0 12 | */ 13 | namespace Smile\ElasticsuiteCms\Controller\Result; 14 | 15 | /** 16 | * Cms page results. 17 | * 18 | * @SuppressWarnings(PHPMD.CouplingBetweenObjects) 19 | * 20 | * @category Smile 21 | * @package Smile\ElasticsuiteCms 22 | * @author Fanny DECLERCK 23 | */ 24 | class Index extends \Magento\Framework\App\Action\Action 25 | { 26 | /** 27 | * Page Factory 28 | * 29 | * @var \Magento\Framework\View\Result\PageFactory 30 | */ 31 | protected $resultPageFactory; 32 | 33 | /** 34 | * PHP Constructor 35 | * 36 | * @param \Magento\Framework\App\Action\Context $context Action context 37 | * @param \Magento\Framework\View\Result\PageFactory $pageFactory Page Factory 38 | * 39 | * @return Index 40 | */ 41 | public function __construct( 42 | \Magento\Framework\App\Action\Context $context, 43 | \Magento\Framework\View\Result\PageFactory $pageFactory 44 | ) { 45 | parent::__construct($context); 46 | 47 | $this->resultPageFactory = $pageFactory; 48 | } 49 | 50 | /** 51 | * Execute the action 52 | * 53 | * @return \Magento\Framework\View\Result\Page 54 | */ 55 | public function execute() 56 | { 57 | $resultPage = $this->resultPageFactory->create(); 58 | 59 | return $resultPage; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Helper/Configuration.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2016 Smile 12 | * @license Open Software License ("OSL") v. 3.0 13 | */ 14 | 15 | namespace Smile\ElasticsuiteCms\Helper; 16 | 17 | use Smile\ElasticsuiteCore\Helper\AbstractConfiguration; 18 | use Magento\Store\Model\StoreManagerInterface; 19 | 20 | /** 21 | * Smile_ElasticsuiteCore search engine configuration default implementation. 22 | * 23 | * @category Smile 24 | * @package Smile\ElasticsuiteCms 25 | * @author Fanny DECLERCK 26 | */ 27 | class Configuration extends AbstractConfiguration 28 | { 29 | /** 30 | * Location of Elasticsuite cms page settings configuration. 31 | * 32 | * @var string 33 | */ 34 | const CONFIG_XML_PREFIX = 'smile_elasticsuite_cms/cms_settings'; 35 | 36 | /** 37 | * Retrieve a configuration value by its key 38 | * 39 | * @param string $key The configuration key 40 | * 41 | * @return mixed 42 | */ 43 | public function getConfigValue($key) 44 | { 45 | return $this->scopeConfig->getValue(self::CONFIG_XML_PREFIX . "/" . $key); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Preconditions 4 | 5 | 6 | 7 | Magento Version : 8 | 9 | 10 | Module Elasticsuite Cms Search Version : 11 | 12 | 13 | Environment : 14 | 15 | 16 | Third party modules : 17 | 18 | ### Steps to reproduce 19 | 20 | 1. 21 | 2. 22 | 3. 23 | 24 | ### Expected result 25 | 26 | 1. 27 | 28 | ### Actual result 29 | 30 | 1. [Screenshot, logs] 31 | 32 | 33 | -------------------------------------------------------------------------------- /Model/Autocomplete/Page/DataProvider.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2016 Smile 12 | * @license Open Software License ("OSL") v. 3.0 13 | */ 14 | namespace Smile\ElasticsuiteCms\Model\Autocomplete\Page; 15 | 16 | use Magento\Search\Model\Autocomplete\DataProviderInterface; 17 | use Magento\Search\Model\QueryFactory; 18 | use Magento\Search\Model\Autocomplete\ItemFactory; 19 | use Magento\Store\Model\StoreManagerInterface; 20 | use Smile\ElasticsuiteCore\Helper\Autocomplete as ConfigurationHelper; 21 | use Smile\ElasticsuiteCms\Model\ResourceModel\Page\Fulltext\CollectionFactory as CmsCollectionFactory; 22 | use Smile\ElasticsuiteCore\Model\Autocomplete\Terms\DataProvider as TermDataProvider; 23 | use Magento\Framework\Event\ManagerInterface as EventManager; 24 | 25 | /** 26 | * Catalog product autocomplete data provider. 27 | * 28 | * @category Smile 29 | * @package Smile\ElasticsuiteCms 30 | * @author Fanny DECLERCK 31 | */ 32 | class DataProvider implements DataProviderInterface 33 | { 34 | /** 35 | * Autocomplete type 36 | */ 37 | const AUTOCOMPLETE_TYPE = "cms_page"; 38 | 39 | /** 40 | * Autocomplete result item factory 41 | * 42 | * @var ItemFactory 43 | */ 44 | protected $itemFactory; 45 | 46 | /** 47 | * Query factory 48 | * 49 | * @var QueryFactory 50 | */ 51 | protected $queryFactory; 52 | 53 | /** 54 | * @var TermDataProvider 55 | */ 56 | protected $termDataProvider; 57 | 58 | /** 59 | * @var CmsCollectionFactory 60 | */ 61 | protected $cmsCollectionFactory; 62 | 63 | /** 64 | * @var ConfigurationHelper 65 | */ 66 | protected $configurationHelper; 67 | 68 | /** 69 | * Store manager 70 | * 71 | * @var \Magento\Store\Model\StoreManagerInterface 72 | */ 73 | protected $storeManager; 74 | 75 | /** 76 | * @var EventManager 77 | */ 78 | protected $eventManager; 79 | 80 | /** 81 | * @var string Autocomplete result type 82 | */ 83 | private $type; 84 | 85 | /** 86 | * Constructor. 87 | * 88 | * @param ItemFactory $itemFactory Suggest item factory. 89 | * @param QueryFactory $queryFactory Search query factory. 90 | * @param TermDataProvider $termDataProvider Search terms suggester. 91 | * @param CmsCollectionFactory $cmsCollectionFactory Cms collection factory. 92 | * @param ConfigurationHelper $configurationHelper Autocomplete configuration helper. 93 | * @param StoreManagerInterface $storeManager Store manager. 94 | * @param EventManager $eventManager Event dispatcher 95 | * @param string $type Autocomplete provider type. 96 | */ 97 | public function __construct( 98 | ItemFactory $itemFactory, 99 | QueryFactory $queryFactory, 100 | TermDataProvider $termDataProvider, 101 | CmsCollectionFactory $cmsCollectionFactory, 102 | ConfigurationHelper $configurationHelper, 103 | StoreManagerInterface $storeManager, 104 | EventManager $eventManager, 105 | $type = self::AUTOCOMPLETE_TYPE 106 | ) { 107 | $this->itemFactory = $itemFactory; 108 | $this->queryFactory = $queryFactory; 109 | $this->termDataProvider = $termDataProvider; 110 | $this->cmsCollectionFactory = $cmsCollectionFactory; 111 | $this->configurationHelper = $configurationHelper; 112 | $this->eventManager = $eventManager; 113 | $this->type = $type; 114 | $this->storeManager = $storeManager; 115 | } 116 | /** 117 | * @return string 118 | */ 119 | public function getType() 120 | { 121 | return $this->type; 122 | } 123 | 124 | /** 125 | * {@inheritDoc} 126 | */ 127 | public function getItems() 128 | { 129 | $result = []; 130 | if ($this->configurationHelper->isEnabled($this->getType())) { 131 | $pageCollection = $this->getCmsPageCollection(); 132 | if ($pageCollection) { 133 | foreach ($pageCollection as $page) { 134 | $item = $this->itemFactory->create( 135 | [ 136 | 'title' => $page->getTitle(), 137 | 'url' => $this->storeManager->getStore()->getBaseUrl(). $page->getIdentifier(), 138 | 'type' => $this->getType(), 139 | ] 140 | ); 141 | 142 | $this->eventManager->dispatch( 143 | 'smile_elasticsuite_cms_search_autocomplete_page_item', 144 | ['page' => $page, 'item' => $item] 145 | ); 146 | 147 | $result[] = $item; 148 | } 149 | } 150 | } 151 | 152 | return $result; 153 | } 154 | 155 | /** 156 | * List of search terms suggested by the search terms data daprovider. 157 | * 158 | * @return array 159 | */ 160 | private function getSuggestedTerms() 161 | { 162 | $terms = array_map( 163 | function (\Magento\Search\Model\Autocomplete\Item $termItem) { 164 | return $termItem->getTitle(); 165 | }, 166 | $this->termDataProvider->getItems() 167 | ); 168 | 169 | return $terms; 170 | } 171 | 172 | /** 173 | * Suggested pages collection. 174 | * Returns null if no suggested search terms. 175 | * 176 | * @return \Smile\ElasticsuiteCms\Model\ResourceModel\Page\Fulltext\Collection|null 177 | */ 178 | private function getCmsPageCollection() 179 | { 180 | $pageCollection = $this->cmsCollectionFactory->create(); 181 | 182 | $pageCollection->setPageSize($this->getResultsPageSize()); 183 | 184 | $queryText = $this->queryFactory->get()->getQueryText(); 185 | $pageCollection->addSearchFilter($queryText); 186 | 187 | return $pageCollection; 188 | } 189 | 190 | /** 191 | * Retrieve number of pages to display in autocomplete results 192 | * 193 | * @return int 194 | */ 195 | private function getResultsPageSize() 196 | { 197 | return $this->configurationHelper->getMaxSize($this->getType()); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /Model/Page/Indexer/Fulltext.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2016 Smile 11 | * @license Open Software License ("OSL") v. 3.0 12 | */ 13 | namespace Smile\ElasticsuiteCms\Model\Page\Indexer; 14 | 15 | use Magento\Framework\Search\Request\DimensionFactory; 16 | use Magento\Framework\Indexer\SaveHandler\IndexerInterface; 17 | use Magento\Store\Model\StoreManagerInterface; 18 | use Smile\ElasticsuiteCms\Model\Page\Indexer\Fulltext\Action\Full; 19 | use Magento\Framework\Indexer\ActionInterface; 20 | use Magento\Framework\Mview\ActionInterface as MviewActionInterface; 21 | 22 | /** 23 | * Categories fulltext indexer 24 | * 25 | * @category Smile 26 | * @package Smile\ElasticsuiteCms 27 | * @author Fanny DECLERCK 28 | */ 29 | class Fulltext implements ActionInterface, MviewActionInterface 30 | { 31 | /** 32 | * @var string 33 | */ 34 | const INDEXER_ID = 'elasticsuite_cms_page_fulltext'; 35 | 36 | /** 37 | * @var IndexerInterface 38 | */ 39 | private $indexerHandler; 40 | 41 | /** 42 | * @var StoreManagerInterface 43 | */ 44 | private $storeManager; 45 | 46 | /** 47 | * @var DimensionFactory 48 | */ 49 | private $dimensionFactory; 50 | 51 | /** 52 | * @var Full 53 | */ 54 | private $fullAction; 55 | 56 | /** 57 | * @param Full $fullAction The full index action 58 | * @param IndexerInterface $indexerHandler The index handler 59 | * @param StoreManagerInterface $storeManager The Store Manager 60 | * @param DimensionFactory $dimensionFactory The dimension factory 61 | */ 62 | public function __construct( 63 | Full $fullAction, 64 | IndexerInterface $indexerHandler, 65 | StoreManagerInterface $storeManager, 66 | DimensionFactory $dimensionFactory 67 | ) { 68 | $this->fullAction = $fullAction; 69 | $this->indexerHandler = $indexerHandler; 70 | $this->storeManager = $storeManager; 71 | $this->dimensionFactory = $dimensionFactory; 72 | } 73 | 74 | /** 75 | * Execute materialization on ids entities 76 | * 77 | * @param int[] $ids The ids 78 | * 79 | * @return void 80 | */ 81 | public function execute($ids) 82 | { 83 | $storeIds = array_keys($this->storeManager->getStores()); 84 | 85 | foreach ($storeIds as $storeId) { 86 | $dimension = $this->dimensionFactory->create(['name' => 'scope', 'value' => $storeId]); 87 | $this->indexerHandler->deleteIndex([$dimension], new \ArrayObject($ids)); 88 | $this->indexerHandler->saveIndex([$dimension], $this->fullAction->rebuildStoreIndex($storeId, $ids)); 89 | } 90 | } 91 | 92 | /** 93 | * Execute full indexation 94 | * 95 | * @return void 96 | */ 97 | public function executeFull() 98 | { 99 | $storeIds = array_keys($this->storeManager->getStores()); 100 | 101 | foreach ($storeIds as $storeId) { 102 | $dimension = $this->dimensionFactory->create(['name' => 'scope', 'value' => $storeId]); 103 | $this->indexerHandler->cleanIndex([$dimension]); 104 | $this->indexerHandler->saveIndex([$dimension], $this->fullAction->rebuildStoreIndex($storeId)); 105 | } 106 | } 107 | 108 | /** 109 | * {@inheritDoc} 110 | */ 111 | public function executeList(array $pageIds) 112 | { 113 | $this->execute($pageIds); 114 | } 115 | 116 | /** 117 | * {@inheritDoc} 118 | */ 119 | public function executeRow($pageId) 120 | { 121 | $this->execute([$pageId]); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Model/Page/Indexer/Fulltext/Action/Full.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2016 Smile 11 | * @license Open Software License ("OSL") v. 3.0 12 | */ 13 | namespace Smile\ElasticsuiteCms\Model\Page\Indexer\Fulltext\Action; 14 | 15 | use Magento\Framework\Filter\RemoveTags; 16 | use Smile\ElasticsuiteCms\Model\ResourceModel\Page\Indexer\Fulltext\Action\Full as ResourceModel; 17 | use Magento\Cms\Model\Template\FilterProvider; 18 | use Magento\Framework\App\Area; 19 | use Magento\Framework\App\AreaList; 20 | use Magento\Store\Model\App\Emulation; 21 | 22 | /** 23 | * ElasticSearch CMS Pages full indexer 24 | * 25 | * @category Smile 26 | * @package Smile\ElasticsuiteCms 27 | * @author Fanny DECLERCK 28 | */ 29 | class Full 30 | { 31 | /** 32 | * @var \Smile\ElasticsuiteCms\Model\ResourceModel\Page\Indexer\Fulltext\Action\Full 33 | */ 34 | private $resourceModel; 35 | 36 | /** 37 | * @var \Magento\Cms\Model\Template\FilterProvider 38 | */ 39 | private $filterProvider; 40 | 41 | /** 42 | * @var \Magento\Framework\App\AreaList 43 | */ 44 | private $areaList; 45 | 46 | /** 47 | * @var \Magento\Framework\Filter\RemoveTags 48 | */ 49 | private $stripTags; 50 | 51 | /** 52 | * Constructor. 53 | * 54 | * @param ResourceModel $resourceModel Indexer resource model. 55 | * @param FilterProvider $filterProvider Model template filter provider. 56 | * @param AreaList $areaList Area List 57 | * @param RemoveTags $stripTags HTML Tags remover 58 | */ 59 | public function __construct( 60 | ResourceModel $resourceModel, 61 | FilterProvider $filterProvider, 62 | AreaList $areaList, 63 | RemoveTags $stripTags 64 | ) { 65 | $this->resourceModel = $resourceModel; 66 | $this->filterProvider = $filterProvider; 67 | $this->areaList = $areaList; 68 | $this->stripTags = $stripTags; 69 | } 70 | 71 | /** 72 | * Get data for a list of cms in a store id. 73 | * 74 | * @param integer $storeId Store id. 75 | * @param array|null $cmsPageIds List of cms page ids. 76 | * 77 | * @return \Traversable 78 | */ 79 | public function rebuildStoreIndex($storeId, $cmsPageIds = null) 80 | { 81 | $lastCmsPageId = 0; 82 | 83 | try { 84 | $this->areaList->getArea(Area::AREA_FRONTEND)->load(Area::PART_DESIGN); 85 | } catch (\InvalidArgumentException | \LogicException $exception) { 86 | // Can occur especially when magento sample data are triggering a full reindex. 87 | ; 88 | } 89 | 90 | do { 91 | $cmsPages = $this->getSearchableCmsPage($storeId, $cmsPageIds, $lastCmsPageId); 92 | foreach ($cmsPages as $pageData) { 93 | $pageData = $this->processPageData($pageData); 94 | $lastCmsPageId = (int) $pageData['page_id']; 95 | yield $lastCmsPageId => $pageData; 96 | } 97 | } while (!empty($cmsPages)); 98 | } 99 | 100 | /** 101 | * Load a bulk of cms page data. 102 | * 103 | * @param int $storeId Store id. 104 | * @param string $cmsPageIds Cms page ids filter. 105 | * @param integer $fromId Load product with id greater than. 106 | * @param integer $limit Number of product to get loaded. 107 | * 108 | * @return array 109 | */ 110 | private function getSearchableCmsPage($storeId, $cmsPageIds = null, $fromId = 0, $limit = 100) 111 | { 112 | return $this->resourceModel->getSearchableCmsPage($storeId, $cmsPageIds, $fromId, $limit); 113 | } 114 | 115 | /** 116 | * Parse template processor cms page content 117 | * 118 | * @param array $pageData Cms page data. 119 | * 120 | * @return array 121 | */ 122 | private function processPageData($pageData) 123 | { 124 | if (isset($pageData['content'])) { 125 | $content = html_entity_decode($this->filterProvider->getPageFilter()->filter($pageData['content'])); 126 | $content = $this->stripTags->filter($content); 127 | $content = preg_replace('/\s\s+/', ' ', $content); 128 | $pageData['content'] = $content; 129 | } 130 | 131 | return $pageData; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Model/ResourceModel/Page/Fulltext/Collection.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2016 Smile 12 | * @license Open Software License ("OSL") v. 3.0 13 | */ 14 | namespace Smile\ElasticsuiteCms\Model\ResourceModel\Page\Fulltext; 15 | 16 | use Smile\ElasticsuiteCore\Search\RequestInterface; 17 | use Smile\ElasticsuiteCore\Search\Request\BucketInterface; 18 | use Magento\Framework\DB\Select; 19 | 20 | /** 21 | * Search engine product collection. 22 | * 23 | * @SuppressWarnings(PHPMD.CouplingBetweenObjects) 24 | * 25 | * @category Smile 26 | * @package Smile\ElasticsuiteCms 27 | * @author Fanny DECLERCK 28 | */ 29 | class Collection extends \Magento\Cms\Model\ResourceModel\Page\Collection 30 | { 31 | /** 32 | * @var QueryResponse 33 | */ 34 | private $queryResponse; 35 | 36 | /** 37 | * @var \Smile\ElasticsuiteCore\Search\Request\Builder 38 | */ 39 | private $requestBuilder; 40 | 41 | /** 42 | * @var \Magento\Search\Model\SearchEngine 43 | */ 44 | private $searchEngine; 45 | 46 | /** 47 | * @var string 48 | */ 49 | private $queryText; 50 | 51 | /** 52 | * @var string 53 | */ 54 | private $searchRequestName; 55 | 56 | /** 57 | * @var array 58 | */ 59 | private $filters = []; 60 | 61 | /** 62 | * @var array 63 | */ 64 | private $facets = []; 65 | 66 | /** 67 | * @var integer 68 | */ 69 | private $storeId; 70 | 71 | /** 72 | * Constructor. 73 | * 74 | * @SuppressWarnings(PHPMD.ExcessiveParameterList) 75 | * 76 | * @param \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory Collection entity factory 77 | * @param \Psr\Log\LoggerInterface $logger Logger. 78 | * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy Db Fetch strategy. 79 | * @param \Magento\Framework\Event\ManagerInterface $eventManager Event manager. 80 | * @param \Magento\Store\Model\StoreManagerInterface $storeManager Store manager. 81 | * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool Metadata pool. 82 | * @param \Smile\ElasticsuiteCore\Search\Request\Builder $requestBuilder Search request 83 | * builder. 84 | * @param \Magento\Search\Model\SearchEngine $searchEngine Search engine 85 | * @param string $searchRequestName Search request 86 | * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection Db Connection. 87 | * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb|null $resource DB connection. 88 | * 89 | */ 90 | public function __construct( 91 | \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory, 92 | \Psr\Log\LoggerInterface $logger, 93 | \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy, 94 | \Magento\Framework\Event\ManagerInterface $eventManager, 95 | \Magento\Store\Model\StoreManagerInterface $storeManager, 96 | \Magento\Framework\EntityManager\MetadataPool $metadataPool, 97 | \Smile\ElasticsuiteCore\Search\Request\Builder $requestBuilder, 98 | \Magento\Search\Model\SearchEngine $searchEngine, 99 | $searchRequestName = 'cms_search_container', 100 | ?\Magento\Framework\DB\Adapter\AdapterInterface $connection = null, 101 | ?\Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null 102 | ) { 103 | 104 | parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $storeManager, $metadataPool, $connection, $resource); 105 | 106 | $this->requestBuilder = $requestBuilder; 107 | $this->searchEngine = $searchEngine; 108 | $this->searchRequestName = $searchRequestName; 109 | } 110 | 111 | /** 112 | * {@inheritDoc} 113 | */ 114 | public function getSize() 115 | { 116 | if ($this->_totalRecords === null) { 117 | $this->loadCmsPageCounts(); 118 | } 119 | 120 | return $this->_totalRecords; 121 | } 122 | 123 | /** 124 | * {@inheritDoc} 125 | */ 126 | public function setOrder($attribute, $dir = \Magento\Framework\DB\Select::SQL_DESC) 127 | { 128 | throw new \LogicException("Sorting on multiple stores is not allowed in search engine collections."); 129 | } 130 | 131 | /** 132 | * Add filter by store 133 | * 134 | * @param int|array|\Magento\Store\Model\Store $storeId Store id 135 | * 136 | * @return $this 137 | */ 138 | public function setStoreId($storeId) 139 | { 140 | $this->storeId = $storeId; 141 | 142 | return $this; 143 | } 144 | 145 | /** 146 | * Returns current store id. 147 | * 148 | * @return int 149 | */ 150 | public function getStoreId() 151 | { 152 | return $this->storeId; 153 | } 154 | 155 | /** 156 | * Add filter by store 157 | * 158 | * @SuppressWarnings(PHPMD.BooleanArgumentFlag) Method is inherited 159 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) Method is inherited 160 | * 161 | * @param int|array|\Magento\Store\Model\Store $store Store 162 | * @param bool $withAdmin With admin 163 | * 164 | * @return $this 165 | */ 166 | public function addStoreFilter($store, $withAdmin = true) 167 | { 168 | if (is_object($store)) { 169 | $store = $store->getId(); 170 | } 171 | 172 | if (is_array($store)) { 173 | throw new \LogicException("Filtering on multiple stores is not allowed in search engine collections."); 174 | } 175 | 176 | return $this->setStoreId($store); 177 | } 178 | 179 | /** 180 | * Add search query filter 181 | * 182 | * @param string $query Search query text. 183 | * 184 | * @return \Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Fulltext\Collection 185 | */ 186 | public function addSearchFilter($query) 187 | { 188 | $this->queryText = $query; 189 | 190 | return $this; 191 | } 192 | 193 | /** 194 | * {@inheritDoc} 195 | */ 196 | public function addFieldToFilter($field, $condition = null) 197 | { 198 | $this->filters[$field] = $condition; 199 | 200 | return $this; 201 | } 202 | 203 | /** 204 | * @SuppressWarnings(PHPMD.CamelCaseMethodName) 205 | * 206 | * {@inheritdoc} 207 | */ 208 | protected function _renderFiltersBefore() 209 | { 210 | $searchRequest = $this->prepareRequest(); 211 | 212 | $this->queryResponse = $this->searchEngine->search($searchRequest); 213 | 214 | // Update the product count. 215 | $this->_totalRecords = $this->queryResponse->count(); 216 | 217 | // Filter search results. The pagination has to be resetted since it is managed by the engine itself. 218 | $docIds = array_map( 219 | function (\Magento\Framework\Api\Search\Document $doc) { 220 | return (int) $doc->getId(); 221 | }, 222 | $this->queryResponse->getIterator()->getArrayCopy() 223 | ); 224 | 225 | if (empty($docIds)) { 226 | $docIds[] = 0; 227 | } 228 | 229 | $this->getSelect()->where('main_table.page_id IN (?)', ['in' => $docIds]); 230 | $this->_pageSize = false; 231 | 232 | return parent::_renderFiltersBefore(); 233 | } 234 | 235 | /** 236 | * @SuppressWarnings(PHPMD.CamelCaseMethodName) 237 | * 238 | * {@inheritDoc} 239 | */ 240 | protected function _afterLoad() 241 | { 242 | // Resort items according the search response. 243 | $orginalItems = $this->_items; 244 | $this->_items = []; 245 | 246 | foreach ($this->queryResponse->getIterator() as $document) { 247 | $documentId = $document->getId(); 248 | if (isset($orginalItems[$documentId])) { 249 | $this->_items[$documentId] = $orginalItems[$documentId]; 250 | $this->_items[$documentId]->setStoreId($this->storeId); 251 | } 252 | } 253 | 254 | return parent::_afterLoad(); 255 | } 256 | 257 | /** 258 | * Prepare the search request before it will be executed. 259 | * 260 | * @return RequestInterface 261 | */ 262 | private function prepareRequest() 263 | { 264 | // Store id and request name. 265 | $storeId = $this->storeId; 266 | $searchRequestName = $this->searchRequestName; 267 | 268 | // Pagination params. 269 | $size = $this->_pageSize ? $this->_pageSize : 20; 270 | $from = $size * (max(1, $this->_curPage) - 1); 271 | 272 | // Query text. 273 | $queryText = $this->queryText; 274 | 275 | // Setup sort orders. 276 | $sortOrders = $this->prepareSortOrders(); 277 | 278 | $searchRequest = $this->requestBuilder->create( 279 | $storeId, 280 | $searchRequestName, 281 | $from, 282 | $size, 283 | $queryText, 284 | $sortOrders, 285 | $this->filters, 286 | $this->facets 287 | ); 288 | 289 | return $searchRequest; 290 | } 291 | 292 | /** 293 | * Prepare sort orders for the request builder. 294 | * 295 | * @return array() 296 | */ 297 | private function prepareSortOrders() 298 | { 299 | $sortOrders = []; 300 | 301 | foreach ($this->_orders as $attribute => $direction) { 302 | $sortParams = ['direction' => $direction]; 303 | $sortField = $this->mapFieldName($attribute); 304 | $sortOrders[$sortField] = $sortParams; 305 | } 306 | 307 | return $sortOrders; 308 | } 309 | 310 | /** 311 | * Convert standard field name to ES fieldname. 312 | * (eg. category_ids => category.category_id). 313 | * 314 | * @param string $fieldName Field name to be mapped. 315 | * 316 | * @return string 317 | */ 318 | private function mapFieldName($fieldName) 319 | { 320 | if (isset($this->fieldNameMapping[$fieldName])) { 321 | $fieldName = $this->fieldNameMapping[$fieldName]; 322 | } 323 | 324 | return $fieldName; 325 | } 326 | 327 | /** 328 | * Load cms page collection size. 329 | * 330 | * @return void 331 | */ 332 | private function loadCmsPageCounts() 333 | { 334 | $storeId = $this->getStoreId(); 335 | $requestName = $this->searchRequestName; 336 | 337 | // Query text. 338 | $queryText = $this->queryText; 339 | 340 | $searchRequest = $this->requestBuilder->create($storeId, $requestName, 0, 0, $queryText, [], $this->filters); 341 | 342 | $searchResponse = $this->searchEngine->search($searchRequest); 343 | 344 | $this->_totalRecords = $searchResponse->count(); 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /Model/ResourceModel/Page/Indexer/Fulltext/Action/Full.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2016 Smile 11 | * @license Open Software License ("OSL") v. 3.0 12 | */ 13 | 14 | namespace Smile\ElasticsuiteCms\Model\ResourceModel\Page\Indexer\Fulltext\Action; 15 | 16 | use Magento\Cms\Api\Data\PageInterface; 17 | use Magento\Framework\App\ResourceConnection; 18 | use Magento\Framework\EntityManager\MetadataPool; 19 | use Magento\Store\Model\StoreManagerInterface; 20 | use Smile\ElasticsuiteCore\Model\ResourceModel\Indexer\AbstractIndexer; 21 | 22 | /** 23 | * ElasticSearch category full indexer resource model. 24 | * 25 | * @category Smile 26 | * @package Smile\ElasticsuiteCms 27 | * @author Fanny DECLERCK 28 | */ 29 | class Full extends AbstractIndexer 30 | { 31 | /** 32 | * @var \Magento\Framework\EntityManager\MetadataPool 33 | */ 34 | private $metadataPool; 35 | 36 | /** 37 | * Full constructor. 38 | * 39 | * @param \Magento\Framework\App\ResourceConnection $resource Resource Connection 40 | * @param \Magento\Store\Model\StoreManagerInterface $storeManager Store Manager 41 | * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool Metadata Pool 42 | */ 43 | public function __construct( 44 | ResourceConnection $resource, 45 | StoreManagerInterface $storeManager, 46 | MetadataPool $metadataPool 47 | ) { 48 | $this->metadataPool = $metadataPool; 49 | parent::__construct($resource, $storeManager); 50 | } 51 | 52 | /** 53 | * Load a bulk of cms page data. 54 | * 55 | * @param int $storeId Store id. 56 | * @param string $cmsPageIds Cms page ids filter. 57 | * @param integer $fromId Load product with id greater than. 58 | * @param integer $limit Number of product to get loaded. 59 | * 60 | * @return array 61 | */ 62 | public function getSearchableCmsPage($storeId, $cmsPageIds = null, $fromId = 0, $limit = 100) 63 | { 64 | $select = $this->getConnection()->select() 65 | ->from(['p' => $this->getTable('cms_page')]); 66 | 67 | $this->addIsVisibleInStoreFilter($select, $storeId); 68 | 69 | if ($cmsPageIds !== null) { 70 | $select->where('p.page_id IN (?)', $cmsPageIds); 71 | } 72 | 73 | $select->where('p.page_id > ?', $fromId) 74 | ->where('p.is_active = ?', true) 75 | ->where('p.is_searchable = ?', true) 76 | ->limit($limit) 77 | ->order('p.page_id'); 78 | 79 | return $this->connection->fetchAll($select); 80 | } 81 | 82 | /** 83 | * Filter the select to append only cms page of current store. 84 | * 85 | * @param \Zend_Db_Select $select Product select to be filtered. 86 | * @param integer $storeId Store Id 87 | * 88 | * @return \Smile\ElasticsuiteCms\Model\ResourceModel\Page\Indexer\Fulltext\Action\Full Self Reference 89 | */ 90 | private function addIsVisibleInStoreFilter($select, $storeId) 91 | { 92 | $linkField = $this->metadataPool->getMetadata(PageInterface::class)->getLinkField(); 93 | 94 | $select->join( 95 | ['ps' => $this->getTable('cms_page_store')], 96 | "p.$linkField = ps.$linkField" 97 | ); 98 | $select->where('ps.store_id IN (?)', [0, $storeId]); 99 | 100 | return $this; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Plugin/Indexer/Page/Save/ReindexPageAfterSave.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2016 Smile 11 | * @license Open Software License ("OSL") v. 3.0 12 | */ 13 | namespace Smile\ElasticsuiteCms\Plugin\Indexer\Page\Save; 14 | 15 | use Magento\Framework\Indexer\IndexerRegistry; 16 | use Smile\ElasticsuiteCms\Model\Page\Indexer\Fulltext; 17 | 18 | /** 19 | * Plugin that proceed cms page reindex in ES after cms page save 20 | * 21 | * @category Smile 22 | * @package Smile\ElasticsuiteCms 23 | * @author Fanny DECLERCK 24 | */ 25 | class ReindexPageAfterSave 26 | { 27 | /** 28 | * @var \Magento\Framework\Indexer\IndexerRegistry 29 | */ 30 | private $indexerRegistry; 31 | 32 | /** 33 | * ReindexCategoryAfterSave constructor. 34 | * 35 | * @param IndexerRegistry $indexerRegistry The indexer registry 36 | */ 37 | public function __construct(IndexerRegistry $indexerRegistry) 38 | { 39 | $this->indexerRegistry = $indexerRegistry; 40 | } 41 | 42 | /** 43 | * Reindex cms page's data into search engine after saving the cms page. 44 | * 45 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 46 | * 47 | * @param \Magento\Cms\Model\ResourceModel\Page $subject The resource model. 48 | * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $result Result of save() method. 49 | * @param \Magento\Framework\Model\AbstractModel $page The CMS page being reindexed. 50 | * 51 | * @return \Magento\Cms\Model\Page 52 | */ 53 | public function afterSave( 54 | \Magento\Cms\Model\ResourceModel\Page $subject, 55 | \Magento\Framework\Model\ResourceModel\Db\AbstractDb $result, 56 | \Magento\Framework\Model\AbstractModel $page 57 | ) { 58 | $isSearchable = $page->getIsSearchable(); 59 | $wasSearchable = ($page->dataHasChangedFor('is_searchable') && (int) $page->getOrigData('is_searchable') === 1); 60 | if ($isSearchable || $wasSearchable) { 61 | $cmsPageIndexer = $this->indexerRegistry->get(Fulltext::INDEXER_ID); 62 | if (!$cmsPageIndexer->isScheduled()) { 63 | $cmsPageIndexer->reindexRow($page->getId()); 64 | } 65 | } 66 | 67 | return $result; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ElasticSuite CMS Pages Search 2 | 3 | This module is a plugin for [ElasticSuite](https://github.com/Smile-SA/elasticsuite). 4 | 5 | It allows to index CMS Pages into the search engine and display them into the autocomplete results, and also on the search result page. 6 | 7 | ### ⚠️ Magento versions compatibility : 8 | 9 | **Which version should I use ?** 10 | 11 | Magento Version | Module Version 12 | ----------------------------------------------------|------------------------------------------------------------------------ 13 | Magento **2.0.x** Opensource (CE) / Commerce (EE) |**2.0.x** latest release : ```composer require smile/module-elasticsuite-cms-search ~2.0.0``` 14 | Magento **2.1.x** Opensource (CE) / Commerce (EE) |**2.1.x** latest release : ```composer require smile/module-elasticsuite-cms-search ~2.1.0``` 15 | Magento **2.2.x** Opensource (CE) / Commerce (EE) |**2.1.x** latest release : ```composer require smile/module-elasticsuite-cms-search ~2.1.0``` 16 | 17 | ### Requirements 18 | 19 | The module requires : 20 | 21 | - [ElasticSuite](https://github.com/Smile-SA/elasticsuite) > 2.1.* 22 | 23 | ### How to use 24 | 25 | 1. Install the module via Composer : 26 | 27 | ``` composer require smile/module-elasticsuite-cms-search ``` 28 | 29 | 2. Enable it 30 | 31 | ``` bin/magento module:enable Smile_ElasticsuiteCms ``` 32 | 33 | 3. Install the module and rebuild the DI cache 34 | 35 | ``` bin/magento setup:upgrade ``` 36 | 37 | 4. Process a full reindex of the CMS Page search index 38 | 39 | ``` bin/magento index:reindex elasticsuite_cms_page_fulltext ``` 40 | 41 | 42 | ### How to configure 43 | 44 | > Stores > Configuration > Elasticsuite > CMS settings > Settings 45 | * Max result : Maximum number of results to display in result block. 46 | 47 | > Stores > Configuration > Elasticsuite > Autocomplete > Cms page Autocomplete 48 | * Max size : Maximum number of cms pages to display in autocomplete results. 49 | 50 | ### Fields indexed 51 | 52 | Field | Type 53 | --------------------|----------- 54 | page_id | Integer 55 | title | Varchar 56 | page_layout | Varchar 57 | meta_keywords | Text 58 | meta_description | Text 59 | identifier | Integer 60 | content_heading | Text 61 | content | Text 62 | creation_time | DateTime 63 | update_time | DateTime 64 | is_active | Integer 65 | sort_order | Integer 66 | layout_update_xml | Text 67 | custom_theme | Integer 68 | custom_root_template| Integer 69 | custom_layout_update| Text 70 | custom_theme_from | DateTime 71 | custom_theme_to | DateTime 72 | meta_title | Text 73 | is_searchable | Integer 74 | store_id | Integer 75 | 76 | Index example : 77 | ``` 78 | { 79 | "_index" : "magento2_fr_cms_page_20181024_064926", 80 | "_type" : "page", 81 | "_id" : "5", 82 | "_score" : 1.0, 83 | "_source" : { 84 | "page_id" : "5", 85 | "title" : "About us", 86 | "page_layout" : "1column", 87 | "meta_keywords" : "", 88 | "meta_description" : "", 89 | "identifier" : "about-us", 90 | "content_heading" : "About us", 91 | "content" : "
\n

With more than 230 stores spanning 43 states and growing, Luma is a nationally recognized active wear manufacturer and retailer. We’re passionate about active lifestyles – and it goes way beyond apparel.

\n\n >\n", 92 | "creation_time" : "2017-03-21 16:59:21", 93 | "update_time" : "2018-10-24 06:45:28", 94 | "is_active" : "1", 95 | "sort_order" : "0", 96 | "layout_update_xml" : "", 97 | "custom_theme" : null, 98 | "custom_root_template" : null, 99 | "custom_layout_update_xml" : "", 100 | "custom_theme_from" : null, 101 | "custom_theme_to" : null, 102 | "meta_title" : "", 103 | "is_searchable" : "1", 104 | "store_id" : "0" 105 | } 106 | } 107 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smile/module-elasticsuite-cms-search", 3 | "type": "magento2-module", 4 | "license": "OSL-3.0", 5 | "authors": [ 6 | {"name": "Fanny DECLERCK", "email": "fadec@smile.fr"}, 7 | {"name": "Aurélien FOUCRET", "email": "aurelien.foucret@smile.fr"}, 8 | {"name": "Romain RUAUD", "email": "romain.ruaud@smile.fr"} 9 | ], 10 | "description": "Smile Elasticsuite - Cms Pages Search Module for Smile Elasticsuite.", 11 | "homepage": "https://github.com/Smile-SA/module-elasticsuite-cms-search", 12 | "keywords": [ 13 | "magento", 14 | "magento2", 15 | "elasticsearch", 16 | "cms", 17 | "search", 18 | "merchandising", 19 | "core", 20 | "setup" 21 | ], 22 | "repositories": [ 23 | { 24 | "type": "composer", 25 | "url": "https://repo.magento.com/" 26 | } 27 | ], 28 | "require": { 29 | "magento/framework": ">=103.0.6", 30 | "magento/module-store": ">=101.1.6", 31 | "magento/module-backend": ">=102.0.6", 32 | "magento/module-catalog": ">=104.0.6", 33 | "magento/module-catalog-search": ">=102.0.6", 34 | "magento/magento-composer-installer": "*", 35 | "smile/elasticsuite": "~2.11.0" 36 | }, 37 | "require-dev": { 38 | "smile/magento2-smilelab-quality-suite": "^1.0.4" 39 | }, 40 | "autoload": { 41 | "files": [ 42 | "registration.php" 43 | ], 44 | "psr-4": { 45 | "Smile\\ElasticsuiteCms\\": "" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 |
21 | separator-top 22 | 23 | smile_elasticsuite 24 | Magento_Backend::smile_elasticsuite_cms 25 | 26 | 27 | 28 | 29 | 30 | integer 31 | 32 | 33 | 34 |
35 | 36 |
37 | 38 | 39 | 40 | 41 | integer 42 | 43 | 44 | 45 |
46 |
47 |
48 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 5 22 | 23 | 24 | 25 | 26 | 27 | 5 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /etc/db_schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /etc/db_schema_whitelist.json: -------------------------------------------------------------------------------- 1 | { 2 | "cms_page": { 3 | "column": { 4 | "is_searchable": true 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | cms_page 23 | page 24 | 25 | 26 | 27 | 28 | 29 | cmsPageSearchIndexHandler 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /etc/elasticsuite_indices.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 1 29 | 1 30 | 31 | 32 | 1 33 | 1 34 | 35 | 36 | 1 37 | 1 38 | 39 | 40 | 1 41 | 1 42 | 43 | 44 | 1 45 | 1 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /etc/elasticsuite_search_request.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /etc/frontend/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | Smile\ElasticsuiteCms\Model\Autocomplete\Page\DataProvider 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /etc/frontend/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /etc/indexer.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | Elasticsuite Cms Page Indexing 22 | Reindex Elasticsuite cms pages. 23 | 24 | 25 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /etc/mview.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /i18n/de_AT.csv: -------------------------------------------------------------------------------- 1 | "Yes","Ja" 2 | "No","Nein" 3 | "Smile Elasticsuite CMS Search","Smile Elasticsuite CMS Search" 4 | "Is searchable","Ist durchsuchbar" 5 | "CMS settings","CMS Einstellungen" 6 | "Setting","Einstellungen" 7 | "Max result","Maximale Anzahl an Ergebnissen" 8 | "Maximum number of results to display in result block.","Maximale Anzahl an Ergebnissen im Suchblock" 9 | "Elasticsuite Cms Page Indexing","Elasticsuite Cms Page Indexing" 10 | "Reindex Elasticsuite cms pages.","Elasticsuite CMS Seiten reindizieren." 11 | "Cms page","CMS Seiten" 12 | "Show more.","Mehr anzeigen" 13 | "Results in cms pages.","Suchergebnisse auf Inhaltsseiten" 14 | -------------------------------------------------------------------------------- /i18n/de_DE.csv: -------------------------------------------------------------------------------- 1 | "Yes","Ja" 2 | "No","Nein" 3 | "Smile Elasticsuite CMS Search","Smile Elasticsuite CMS Search" 4 | "Is searchable","Ist durchsuchbar" 5 | "CMS settings","CMS Einstellungen" 6 | "Setting","Einstellungen" 7 | "Max result","Maximale Anzahl an Ergebnissen" 8 | "Maximum number of results to display in result block.","Maximale Anzahl an Ergebnissen im Suchblock" 9 | "Elasticsuite Cms Page Indexing","Elasticsuite Cms Page Indexing" 10 | "Reindex Elasticsuite cms pages.","Elasticsuite CMS Seiten reindizieren." 11 | "Cms page","CMS Seiten" 12 | "Show more.","Mehr anzeigen" 13 | "Results in cms pages.","Suchergebnisse auf Inhaltsseiten" 14 | -------------------------------------------------------------------------------- /i18n/fr_FR.csv: -------------------------------------------------------------------------------- 1 | Yes,Yes 2 | No,No 3 | "Smile Elasticsuite CMS Search","Smile Elasticsuite CMS Search" 4 | "Is searchable","Utiliser dans la recherche" 5 | "CMS settings","Configuration des pages CMS" 6 | "Setting","Configuration" 7 | "Max result","Nombre de résultats maximum" 8 | "Maximum number of results to display in result block.","Nombre maximum de résultats à afficher dans le bloc de résultats de recherche." 9 | "Elasticsuite Cms Page Indexing","Indexer elasticsuite des pages cms" 10 | "Reindex Elasticsuite cms pages.","Réindexation elasticsuite des pages cms" 11 | "Cms page","Page cms" 12 | "Show more","Voir plus" -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2016 Smile 13 | * @license Open Software License ("OSL") v. 3.0 14 | */ 15 | 16 | \Magento\Framework\Component\ComponentRegistrar::register( 17 | \Magento\Framework\Component\ComponentRegistrar::MODULE, 18 | 'Smile_ElasticsuiteCms', 19 | __DIR__ 20 | ); 21 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/cms_page_form.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 |
20 | 21 | 22 | true 23 | Search Engine 24 | 100 25 | 26 | 27 | 28 | 29 | 30 | boolean 31 | Is searchable 32 | checkbox 33 | toggle 34 | page 35 | 10 36 | is_searchable 37 | 38 | 1 39 | 0 40 | 41 | 1 42 | 43 | 44 | 45 |
46 | 47 | -------------------------------------------------------------------------------- /view/adminhtml/ui_component/cms_page_listing.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 1 25 | Yes 26 | 27 | 28 | 0 29 | No 30 | 31 | 32 | 33 | select 34 | Magento_Ui/js/grid/columns/select 35 | select 36 | Is searchable 37 | false 38 | select 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /view/frontend/layout/catalogsearch_result_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /view/frontend/layout/default.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Cms page 24 | Smile_ElasticsuiteCms/autocomplete/cms 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /view/frontend/layout/smile_elasticsuite_cms_result_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | Cms page results Default Page 23 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /view/frontend/templates/cms_page_result.phtml: -------------------------------------------------------------------------------- 1 | 16 | 25 | -------------------------------------------------------------------------------- /view/frontend/templates/cms_page_suggest.phtml: -------------------------------------------------------------------------------- 1 | 16 | canShowBlock()): ?> 17 | 27 | -------------------------------------------------------------------------------- /view/frontend/web/template/autocomplete/cms.html: -------------------------------------------------------------------------------- 1 | 16 |
17 | <%- data.title %> 18 | 19 |
20 | --------------------------------------------------------------------------------