├── .circleci
└── config.yml
├── .gitignore
├── README.TXT
├── composer.json
├── config
└── schema
│ ├── elasticsearch_connector.backend.schema.yml
│ ├── elasticsearch_connector.cluster.schema.yml
│ └── elasticsearch_connector.index.schema.yml
├── css
└── ec-index.css
├── elasticsearch_connector.api.php
├── elasticsearch_connector.info.yml
├── elasticsearch_connector.install
├── elasticsearch_connector.libraries.yml
├── elasticsearch_connector.links.action.yml
├── elasticsearch_connector.links.menu.yml
├── elasticsearch_connector.links.task.yml
├── elasticsearch_connector.module
├── elasticsearch_connector.permissions.yml
├── elasticsearch_connector.routing.yml
├── elasticsearch_connector.services.yml
├── img
├── cancel.png
└── plus.png
├── js
├── ec-index-child.js
└── ec-index.js
├── modules
└── elasticsearch_connector_views
│ ├── elasticsearch_connector_views.info.yml
│ ├── elasticsearch_connector_views.views.inc
│ └── src
│ └── Plugin
│ └── views
│ ├── ElasticsearchViewsHandlerTrait.php
│ ├── field
│ ├── ElasticsearchViewsBoolean.php
│ ├── ElasticsearchViewsDate.php
│ ├── ElasticsearchViewsEntity.php
│ ├── ElasticsearchViewsEntityField.php
│ ├── ElasticsearchViewsFieldTrait.php
│ ├── ElasticsearchViewsMarkup.php
│ ├── ElasticsearchViewsNumeric.php
│ └── ElasticsearchViewsStandard.php
│ ├── filter
│ ├── ElasticsearchViewsBooleanOperator.php
│ ├── ElasticsearchViewsDate.php
│ ├── ElasticsearchViewsFulltextSearch.php
│ ├── ElasticsearchViewsNumericFilter.php
│ ├── ElasticsearchViewsStandard.php
│ └── ElasticsearchViewsStringFilter.php
│ ├── join
│ └── ElasticsearchViewsJoin.php
│ └── query
│ └── ElasticsearchViewsQuery.php
├── phpunit.core.xml.dist
├── src
├── ClusterManager.php
├── Controller
│ ├── ClusterListBuilder.php
│ └── ElasticsearchController.php
├── ElasticSearch
│ ├── ClientManager.php
│ ├── ClientManagerInterface.php
│ └── Parameters
│ │ ├── Builder
│ │ └── SearchBuilder.php
│ │ └── Factory
│ │ ├── FilterFactory.php
│ │ ├── IndexFactory.php
│ │ ├── MappingFactory.php
│ │ └── SearchFactory.php
├── Entity
│ ├── Cluster.php
│ ├── ClusterRouteProvider.php
│ ├── Index.php
│ └── IndexRouteProvider.php
├── Event
│ ├── BuildIndexParamsEvent.php
│ ├── BuildSearchParamsEvent.php
│ ├── PrepareIndexEvent.php
│ ├── PrepareIndexMappingEvent.php
│ ├── PrepareMappingEvent.php
│ └── PrepareSearchQueryEvent.php
├── Exception
│ └── ElasticSearchConnectorException.php
├── Form
│ ├── ClusterDeleteForm.php
│ ├── ClusterForm.php
│ ├── IndexDeleteForm.php
│ └── IndexForm.php
└── Plugin
│ └── search_api
│ ├── backend
│ ├── SearchApiElasticsearchBackend.php
│ └── SearchApiElasticsearchBackendInterface.php
│ ├── data_type
│ └── ObjectDataType.php
│ └── processor
│ └── ExcludeSourceFields.php
└── tests
├── modules
└── elasticsearch_test
│ ├── config
│ └── install
│ │ ├── search_api.index.elasticsearch_index.yml
│ │ └── search_api.server.elasticsearch_server.yml
│ └── elasticsearch_test.info.yml
└── src
├── Behat
├── behat.yml
├── example.behat.local.yml
└── features
│ └── bootstrap
│ ├── ElasticsearchConnectorFeatureContext.php
│ └── settings_form.feature
├── Kernel
└── ElasticsearchTest.php
└── Unit
├── ClusterManagerTest.php
├── ElasticSearch
├── ClientManagerTest.php
└── Parameters
│ ├── Builder
│ └── SearchBuilderTest.php
│ └── Factory
│ ├── FilterFactoryTest.php
│ ├── IndexFactoryTest.php
│ └── MappingFactoryTest.php
└── Entity
├── ClusterRouteProviderTest.php
├── IndexRouteProviderTest.php
└── IndexTest.php
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Default configuration file for Drupal modules.
2 | #
3 | # Use setup.sh to automate setting this up. Otherwise, to use this in a new
4 | # module:
5 | # 1. Copy config.yml to the module's .circleci directory.
6 | # 2. Change 'latest' in the image tag to the latest tag.
7 | # 3. Update the working_directory key.
8 | # 4. Connect CircleCI to the repository through the Circle UI.
9 | # 5. Set the COMPOSER_AUTH environment variable in Circle to grant access to
10 | # any private repositories.
11 | # 6. Create a status badge embed code in Circle and add it to the README.md.
12 | #
13 | # Check https://circleci.com/docs/2.0/language-php/ for more details
14 | #
15 |
16 | defaults: &defaults
17 | docker:
18 | # specify the version you desire here (avoid latest except for testing)
19 | - image: andrewberry/drupal_tests:0.0.11
20 |
21 | # Use our fork until https://github.com/wernight/docker-phantomjs/pull/3 is
22 | # merged.
23 | # - image: wernight/phantomjs:2.1.1
24 | - image: andrewberry/docker-phantomjs:v2.1.1-patch1
25 | command: [phantomjs, --webdriver=8910]
26 |
27 | - image: mariadb:10.3
28 | environment:
29 | MYSQL_ALLOW_EMPTY_PASSWORD: 1
30 |
31 | # Specify service dependencies here if necessary
32 | # CircleCI maintains a library of pre-built images
33 | # documented at https://circleci.com/docs/2.0/circleci-images/
34 | # - image: circleci/mysql:9.4
35 |
36 | # 'checkout' supports a path key, but not on locals where you test with the
37 | # circleci CLI tool.
38 | # https://discuss.circleci.com/t/bug-circleci-build-command-ignores-checkout-path-config/13004
39 | working_directory: /var/www/html/modules/elasticsearch_connector
40 |
41 | # YAML does not support merging of lists. That means we can't have a default
42 | # 'steps' configuration, though we can have defaults for individual step
43 | # properties.
44 |
45 | # We use the composer.json as a way to determine if we can cache our build.
46 | restore_cache: &restore_cache
47 | keys:
48 | - v1-dependencies-{{ checksum "composer.json" }}
49 | # fallback to using the latest cache if no exact match is found
50 | - v1-dependencies-
51 |
52 | # If composer.json hasn't changed, restore the vendor directory. We don't
53 | # restore the lock file so we ensure we get updated dependencies.
54 | save_cache: &save_cache
55 | paths:
56 | - ./vendor
57 | key: v1-dependencies-{{ checksum "composer.json" }}
58 |
59 | # Run Drupal unit and kernel tests as one job. This command invokes the test.sh
60 | # hook.
61 | unit_kernel_tests: &unit_kernel_tests
62 | <<: *defaults
63 | steps:
64 | - checkout
65 |
66 | - restore_cache: *restore_cache
67 | - save_cache: *save_cache
68 |
69 | - run:
70 | working_directory: /var/www/html
71 | command: |
72 | ./test.sh $CIRCLE_PROJECT_REPONAME
73 |
74 | - store_test_results:
75 | path: /var/www/html/artifacts/phpunit
76 | - store_artifacts:
77 | path: /var/www/html/artifacts
78 |
79 | # Run Behat tests. This command invokes the test-js.sh hook.
80 | behat_tests: &behat_tests
81 | <<: *defaults
82 | steps:
83 | - checkout
84 |
85 | - restore_cache: *restore_cache
86 | - save_cache: *save_cache
87 |
88 | - run:
89 | working_directory: /var/www/html
90 | command: |
91 | ./test-js.sh $CIRCLE_PROJECT_REPONAME
92 |
93 | - store_test_results:
94 | path: /var/www/html/artifacts/behat
95 | - store_artifacts:
96 | path: /var/www/html/artifacts
97 |
98 | # Run code quality tests. This invokes code-sniffer.sh.
99 | code_sniffer: &code_sniffer
100 | <<: *defaults
101 | steps:
102 | - checkout
103 |
104 | - restore_cache: *restore_cache
105 | - save_cache: *save_cache
106 |
107 | - run:
108 | working_directory: /var/www/html
109 | command: |
110 | ./code-sniffer.sh $CIRCLE_PROJECT_REPONAME
111 |
112 | - store_test_results:
113 | path: /var/www/html/artifacts/phpcs
114 | - store_artifacts:
115 | path: /var/www/html/artifacts
116 |
117 | # Run code coverage tests. This invokes code-coverage-stats.sh.
118 | code_coverage: &code_coverage
119 | <<: *defaults
120 | steps:
121 | - checkout
122 |
123 | - restore_cache: *restore_cache
124 | - save_cache: *save_cache
125 |
126 | - run:
127 | working_directory: /var/www/html
128 | command: |
129 | ./code-coverage-stats.sh $CIRCLE_PROJECT_REPONAME
130 | - store_artifacts:
131 | path: /var/www/html/artifacts
132 |
133 | # Declare all of the jobs we should run.
134 | version: 2
135 | jobs:
136 | run-unit-kernel-tests:
137 | <<: *unit_kernel_tests
138 | run-behat-tests:
139 | <<: *behat_tests
140 | run-code-sniffer:
141 | <<: *code_sniffer
142 | run-code-coverage:
143 | <<: *code_coverage
144 |
145 | workflows:
146 | version: 2
147 |
148 | # Declare a workflow that runs all of our jobs in parallel.
149 | test_and_lint:
150 | jobs:
151 | - run-unit-kernel-tests
152 | - run-behat-tests
153 | - run-code-sniffer
154 | - run-code-coverage
155 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | composer.lock
3 | .idea
4 | tests/src/Behat/behat.local.yml
5 |
--------------------------------------------------------------------------------
/README.TXT:
--------------------------------------------------------------------------------
1 | Elasticsearch Connector is a set of modules designed to build a full Elasticsearch eco system in Drupal.
2 |
3 | Installation
4 | ============
5 |
6 | Download this module with the following command:
7 |
8 | ```
9 | cd /path/to/drupal
10 | composer require drupal/elasticsearch_connector
11 | ```
12 |
13 | Then, either install it with Drush using `drush en elasticsearch_connector` or via
14 | the administration interface.
15 |
16 | Configuration
17 | =============
18 |
19 | For setting up Drupal to index content into Elasticsearch, follow the
20 | steps at https://www.lullabot.com/articles/indexing-content-from-drupal-8-to-elasticsearch.
21 |
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "drupal/elasticsearch_connector",
3 | "description": "Elasticsearch Connector module for Drupal.",
4 | "type": "drupal-module",
5 | "homepage": "https://www.drupal.org/project/elasticsearch_connector",
6 | "require": {
7 | "nodespark/des-connector": "6.x-dev",
8 | "makinacorpus/php-lucene": "^1.0.2"
9 | },
10 | "repositories": {
11 | "drupal": {
12 | "type": "composer",
13 | "url": "https://packages.drupal.org/7"
14 | }
15 | },
16 | "require-dev": {
17 | "drupal/search_api": "^1.4",
18 | "behat/mink-selenium2-driver": "^1.3",
19 | "drupal/coder": "^8.2",
20 | "drupal/drupal-extension": "master-dev",
21 | "bex/behat-screenshot": "^1.2",
22 | "phpmd/phpmd": "^2.6",
23 | "phpmetrics/phpmetrics": "^2.3"
24 | },
25 | "license": "GPL-2.0+",
26 | "authors": [
27 | {
28 | "name": "Nikolay Ignatov",
29 | "email": "nignatov@nodespark.com",
30 | "homepage": "http://www.nodespark.com",
31 | "role": "Creator and Maintainer"
32 | },
33 | {
34 | "name": "See other contributors",
35 | "homepage": "https://www.drupal.org/node/2159059/committers"
36 | }
37 | ],
38 | "support": {
39 | "issues": "https://www.drupal.org/project/issues/elasticsearch_connector",
40 | "source": "https://github.com/nodespark/elasticsearch_connector"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/config/schema/elasticsearch_connector.backend.schema.yml:
--------------------------------------------------------------------------------
1 | elasticsearch_connector.backend.plugin.elasticsearch:
2 | type: mapping
3 | label: 'Search API Elasticsearch settings'
4 | mapping:
5 | cluster_settings:
6 | type: mapping
7 | label: 'Elasticsearch settings'
8 | mapping:
9 | cluster:
10 | type: string
11 | label: 'The cluster that handles the connection'
12 | scheme:
13 | type: string
14 | label: 'The HTTP protocol to use for sending queries'
15 | host:
16 | type: string
17 | label: 'The host name or IP of the Elasticsearch server'
18 | port:
19 | type: string
20 | label: 'The port of the Elasticsearch server'
21 | path:
22 | type: string
23 | label: 'The path that identifies the Elasticsearch instance to use on the server'
24 | http_user:
25 | type: string
26 | label: 'Username for basic HTTP authentication'
27 | http_pass:
28 | type: string
29 | label: 'Password for basic HTTP authentication'
30 | excerpt:
31 | type: boolean
32 | label: 'Return an excerpt for all results'
33 | retrieve_data:
34 | type: boolean
35 | label: 'Retrieve result data from Elasticsearch'
36 | highlight_data:
37 | type: boolean
38 | label: 'Highlight retrieved data'
39 | http_method:
40 | type: string
41 | label: 'The HTTP method to use for sending queries'
42 | autocorrect_spell:
43 | type: boolean
44 | label: 'Use spellcheck for autocomplete suggestions'
45 | autocorrect_suggest_words:
46 | type: boolean
47 | label: 'Suggest additional words'
48 |
--------------------------------------------------------------------------------
/config/schema/elasticsearch_connector.cluster.schema.yml:
--------------------------------------------------------------------------------
1 | elasticsearch_connector.cluster.*:
2 | type: config_entity
3 | label: 'Elasticsearch Cluster'
4 | mapping:
5 | cluster_id:
6 | type: string
7 | label: 'Cluster ID'
8 | name:
9 | type: string
10 | label: 'Cluster Name'
11 | status:
12 | type: string
13 | label: 'Cluster Status'
14 | url:
15 | type: string
16 | label: 'Cluster URL'
17 | proxy:
18 | type: string
19 | label: 'Cluster Proxy'
20 | options:
21 | type: mapping
22 | label: 'Options'
23 | mapping:
24 | multiple_nodes_connection:
25 | type: boolean
26 | label: 'Multiple Nodes Connection'
27 | locked:
28 | type: boolean
29 | label: 'Locked'
30 |
--------------------------------------------------------------------------------
/config/schema/elasticsearch_connector.index.schema.yml:
--------------------------------------------------------------------------------
1 | elasticsearch_connector.index.*:
2 | type: config_entity
3 | label: 'Elasticsearch Index'
4 | mapping:
5 | index_id:
6 | type: string
7 | label: 'Index ID'
8 | name:
9 | type: string
10 | label: 'Index Name'
11 | num_of_shards:
12 | type: integer
13 | label: 'Shards'
14 | num_of_replica:
15 | type: integer
16 | label: 'Replica'
17 | server:
18 | type: string
19 | label: 'Cluster machine name'
20 |
--------------------------------------------------------------------------------
/css/ec-index.css:
--------------------------------------------------------------------------------
1 | ul.index-dialog-links, ul.index-dialog-links li {
2 | list-style-type: none;
3 | }
4 |
5 | ul.index-dialog-links li, ul.index-dialog-links li a {
6 | display: inline;
7 | }
8 |
9 | ul.index-dialog-links li.active, ul.index-dialog-links li a.active {
10 | color: #0074BD;
11 | }
12 |
13 | ul.index-dialog-links a {
14 | padding-left: 20px;
15 | margin: 5px;
16 | }
17 |
18 | ul.index-dialog-links li a {
19 | background: url('../img/plus.png') left no-repeat;
20 | }
21 |
22 | .es-list-index {
23 | padding-left: 3em;
24 | }
25 |
--------------------------------------------------------------------------------
/elasticsearch_connector.api.php:
--------------------------------------------------------------------------------
1 | array(
19 | 'title' => t('The PHP version is not compatible with this module.'),
20 | 'description' => t('The module requires PHP version bigger than or equal to version 5.3.9.'),
21 | 'severity' => REQUIREMENT_ERROR,
22 | 'value' => t('PHP version not compatible.'),
23 | ),
24 | );
25 | }
26 | }
27 |
28 | if ($phase == 'runtime') {
29 | if (!interface_exists('\nodespark\DESConnector\ClientInterface')) {
30 | return array(
31 | 'elasticsearch_connector' => array(
32 | 'title' => t('The Elasticsearch client library is missing.'),
33 | 'description' => t('The client library for Elasticsearch connection is missing.'),
34 | 'severity' => REQUIREMENT_ERROR,
35 | 'value' => t('Elasticsearch library missing.'),
36 | ),
37 | );
38 | }
39 | else {
40 | return array(
41 | 'elasticsearch_connector' => array(
42 | 'title' => t('Elasticsearch PHP client library'),
43 | 'description' => t('The client library for Elasticsearch was correctly installed.'),
44 | 'severity' => REQUIREMENT_OK,
45 | 'value' => t('OK'),
46 | ),
47 | );
48 | }
49 | }
50 |
51 | return array();
52 | }
53 |
--------------------------------------------------------------------------------
/elasticsearch_connector.libraries.yml:
--------------------------------------------------------------------------------
1 | drupal.elasticsearch_connector.ec_index:
2 | version: VERSION
3 | css:
4 | theme:
5 | css/ec-index.css: {}
6 |
--------------------------------------------------------------------------------
/elasticsearch_connector.links.action.yml:
--------------------------------------------------------------------------------
1 | entity.elasticsearch_cluster.add_form:
2 | route_name: entity.elasticsearch_cluster.add_form
3 | title: 'Add cluster'
4 | appears_on:
5 | - elasticsearch_connector.config_entity.list
6 | entity.elasticsearch_index.add_form:
7 | route_name: entity.elasticsearch_index.add_form
8 | title: 'Add index'
9 | appears_on:
10 | - elasticsearch_connector.config_entity.list
11 |
--------------------------------------------------------------------------------
/elasticsearch_connector.links.menu.yml:
--------------------------------------------------------------------------------
1 | elasticsearch_connector.config_entity:
2 | title: 'Elasticsearch Connector'
3 | description: 'Administer Elasticsearch clusters and indices.'
4 | route_name: elasticsearch_connector.config_entity.list
5 | parent: system.admin_config_search
6 |
--------------------------------------------------------------------------------
/elasticsearch_connector.links.task.yml:
--------------------------------------------------------------------------------
1 | entity.elasticsearch_connector.cluster.view:
2 | route_name: entity.elasticsearch_cluster.canonical
3 | base_route: entity.elasticsearch_cluster.canonical
4 | title: 'View'
5 |
--------------------------------------------------------------------------------
/elasticsearch_connector.permissions.yml:
--------------------------------------------------------------------------------
1 | administer elasticsearch connector:
2 | title: 'Administer elasticsearch connector'
3 | description: 'Giving you access to administer elasticsearch connector configurations.'
4 | administer elasticsearch cluster:
5 | title: 'Administer elasticsearch cluster'
6 | description: 'Giving you access to administer elasticsearch clusters.'
7 | administer elasticsearch index:
8 | title: 'Administer elasticsearch index'
9 | description: 'Giving you access to administer elasticsearch indices.'
10 |
--------------------------------------------------------------------------------
/elasticsearch_connector.routing.yml:
--------------------------------------------------------------------------------
1 | elasticsearch_connector.config_entity.list:
2 | path: '/admin/config/search/elasticsearch-connector'
3 | defaults:
4 | _title: 'Elasticsearch Connector'
5 | _entity_list: 'elasticsearch_cluster'
6 | requirements:
7 | _permission: 'administer elasticsearch connector'
8 |
--------------------------------------------------------------------------------
/elasticsearch_connector.services.yml:
--------------------------------------------------------------------------------
1 | services:
2 | elasticsearch_connector.client_factory:
3 | class: nodespark\DESConnector\ClientFactory
4 |
5 | elasticsearch_connector.client_manager:
6 | class: Drupal\elasticsearch_connector\ElasticSearch\ClientManager
7 | arguments:
8 | - '@module_handler'
9 | - '@elasticsearch_connector.client_factory'
10 |
11 | elasticsearch_connector.cluster_manager:
12 | class: Drupal\elasticsearch_connector\ClusterManager
13 | arguments: ['@state', '@entity_type.manager']
14 |
15 | elasticsearch_connector.index_factory:
16 | class: Drupal\elasticsearch_connector\ElasticSearch\Parameters\Factory\IndexFactory
17 |
--------------------------------------------------------------------------------
/img/cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodespark/elasticsearch_connector/36a0645ce4d88f2db826972dfaadab9a995ef114/img/cancel.png
--------------------------------------------------------------------------------
/img/plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodespark/elasticsearch_connector/36a0645ce4d88f2db826972dfaadab9a995ef114/img/plus.png
--------------------------------------------------------------------------------
/js/ec-index-child.js:
--------------------------------------------------------------------------------
1 | (function ($) {
2 | /**
3 | * Attach the child dialog behavior to new content.
4 | */
5 | Drupal.behaviors.ECIndexDialogChild = {
6 | attach: function (context, settings) {
7 | // Get the entity id and title from the settings provided by the views display.
8 | var cluster_id = settings.elasticsearch.dialog.cluster_id;
9 | var index_name = settings.elasticsearch.dialog.index_name;
10 | if (cluster_id != null && cluster_id != '') {
11 | // Close the dialog by communicating with the parent.
12 | parent.Drupal.ECIndexDialog.close(cluster_id, index_name, settings.elasticsearch.dialog);
13 | }
14 | }
15 | }
16 | })(jQuery);
17 |
--------------------------------------------------------------------------------
/js/ec-index.js:
--------------------------------------------------------------------------------
1 | (function ($) {
2 |
3 | Drupal.behaviors.ECIndexDialog = {
4 | attach: function (context, settings) {
5 | $('a.ec-index-dialog').once('ec-index-dialog', function () {
6 | $(this).click(function () {
7 |
8 | Drupal.ECIndexDialog.open($(this).attr('href'), $(this).html());
9 | Drupal.ECIndexDialog.populateIndex = function (cluster_id, index_name, settings) {
10 | // Add new option and select it.
11 | $('#' + settings.index_element_id)
12 | .append('')
13 | .val(index_name);
14 |
15 | // Trigger change to update the form cache.
16 | $('#' + settings.cluster_element_id).trigger('change');
17 | };
18 |
19 | return false;
20 | }, context);
21 | });
22 | }
23 | };
24 |
25 | /**
26 | * Our dialog object. Can be used to open a dialog to anywhere.
27 | */
28 | Drupal.ECIndexDialog = {
29 | dialog_open: false,
30 | open_dialog: null
31 | };
32 |
33 | /**
34 | * If this property is set to be a function, it
35 | * will be called when an entity is received from an overlay.
36 | */
37 | Drupal.ECIndexDialog.populateIndex = null;
38 |
39 | /**
40 | * Open a dialog window.
41 | *
42 | * @param href
43 | * @param title
44 | */
45 | Drupal.ECIndexDialog.open = function (href, title) {
46 | if (!this.dialog_open) {
47 | // Get the current window size and do 75% of the width and 90% of the height.
48 | // @todo Add settings for this so that users can configure this by themselves.
49 | var window_width = $(window).width() / 100 * 75;
50 | var window_height = $(window).height() / 100 * 90;
51 | this.open_dialog = $('').dialog({
52 | width: window_width,
53 | height: window_height,
54 | modal: true,
55 | resizable: false,
56 | position: ["center", 50],
57 | title: title,
58 | close: function () {
59 | Drupal.ECIndexDialog.dialog_open = false;
60 | }
61 | }).width(window_width - 10).height(window_height);
62 | $(window).bind("resize scroll", function () {
63 | // Move the dialog the main window moves.
64 | if (typeof Drupal.ECIndexDialog == "object" && Drupal.ECIndexDialog.open_dialog != null) {
65 | Drupal.ECIndexDialog.open_dialog.dialog("option", "position", ["center", 10]);
66 | Drupal.ECIndexDialog.setDimensions();
67 | }
68 | });
69 | this.dialog_open = true;
70 | }
71 | };
72 |
73 | /**
74 | * Set dimensions of the dialog depending on the current window size
75 | * and scroll position.
76 | */
77 | Drupal.ECIndexDialog.setDimensions = function () {
78 | if (typeof Drupal.ECIndexDialog == "object") {
79 | var window_width = $(window).width() / 100 * 75;
80 | var window_height = $(window).height() / 100 * 90;
81 | this.open_dialog.dialog("option", "width", window_width).dialog("option", "height", window_height).width(window_width - 10).height(window_height);
82 | }
83 | };
84 |
85 | /**
86 | * Close the dialog and provide an entity id and a title
87 | * that we can use in various ways.
88 | */
89 | Drupal.ECIndexDialog.close = function (cluster_id, index_name, settings) {
90 | this.open_dialog.dialog('close');
91 | this.open_dialog.dialog('destroy');
92 | this.open_dialog = null;
93 | this.dialog_open = false;
94 | // Call our populateIndex function if we have one.
95 | // this is used as an event.
96 | if (typeof this.populateIndex == "function") {
97 | this.populateIndex(cluster_id, index_name, settings);
98 | }
99 | }
100 | }(jQuery));
101 |
--------------------------------------------------------------------------------
/modules/elasticsearch_connector_views/elasticsearch_connector_views.info.yml:
--------------------------------------------------------------------------------
1 | name:
2 | 'Elasticsearch Connector Views'
3 | description:
4 | 'Stand alone module for integration between Drupal Views and Elasticsearch indexes.'
5 | core: 8.x
6 | package: Elasticsearch
7 | type: module
8 | dependencies:
9 | - drupal:views
10 | - elasticsearch_connector:elasticsearch_connector
11 | version: VERSION
12 |
--------------------------------------------------------------------------------
/modules/elasticsearch_connector_views/elasticsearch_connector_views.views.inc:
--------------------------------------------------------------------------------
1 | loadAllClusters(FALSE) as $cluster) {
21 | $elasticsearchClient = $clientManager->getClientForCluster($cluster);
22 | if ($elasticsearchClient->isClusterOk()) {
23 | $indices = $elasticsearchClient->indices()->stats();
24 | // TODO: Handle aliases also, not only indices.
25 | if (!empty($indices['indices'])) {
26 | foreach ($indices['indices'] as $index_name => $index_info) {
27 | // In elasticsearch the table is actually the document type.
28 | // So get all types and build data array.
29 | $mapping = $elasticsearchClient->indices()
30 | ->getMapping(array('index' => $index_name));
31 | if (!empty($mapping[$index_name]['mappings'])) {
32 | foreach ($mapping[$index_name]['mappings'] as $type_name => $type_settings) {
33 | $name = new FormattableMarkup(
34 | '@cluster (@index_name - @type)', array(
35 | '@cluster' => $cluster->name,
36 | '@index_name' => $index_name,
37 | '@type' => $type_name,
38 | )
39 | );
40 | $base_table = 'elsv__' . $cluster->cluster_id . '__' . $index_name . '__' . $type_name;
41 |
42 | $data[$base_table]['table']['group'] = t('Elasticsearch');
43 | $data[$base_table]['table']['base'] = array(
44 | 'index' => $index_name,
45 | 'cluster_id' => $cluster->cluster_id,
46 | 'type' => $type_name,
47 | 'title' => t('Cluster :name', array(':name' => $name)),
48 | 'help' => t('Searches the site with the Elasticsearch search engine for !name', array('!name' => $name)),
49 | 'query_id' => 'elasticsearch_connector_views_query',
50 | );
51 |
52 | // Get the list of the fields in index directly from Elasticsearch.
53 | if (!empty($type_settings['properties'])) {
54 | _elasticsearch_connector_views_handle_fields($base_table, $data, $type_settings['properties']);
55 | }
56 |
57 | // Keyword field.
58 | $data[$base_table]['keyword'] = array(
59 | 'title' => t('Search'),
60 | 'help' => t('Fulltext search'),
61 | 'filter' => array(
62 | 'id' => 'elasticsearch_connector_views_fulltext_filter',
63 | ),
64 | );
65 |
66 | // Snippet field.
67 | $data[$base_table]['snippet'] = array(
68 | 'title' => t('Snippet'),
69 | 'help' => t('Search snippet'),
70 | 'field' => array(
71 | 'handler' => 'elasticsearch_connector_views_snippet_handler_field',
72 | 'click sortable' => TRUE,
73 | ),
74 | );
75 |
76 | // Score field.
77 | $data[$base_table]['score'] = array(
78 | 'title' => t('Score'),
79 | 'help' => t('Score'),
80 | 'field' => array(
81 | 'id' => 'elasticsearch_connector_views_standard',
82 | 'click sortable' => TRUE,
83 | ),
84 | );
85 | }
86 | }
87 | }
88 | }
89 | }
90 | }
91 |
92 | return $data;
93 | }
94 |
95 | /**
96 | * Handle the fields mapping and handle nested data types.
97 | *
98 | * @param string $base_table
99 | * The base table value.
100 | * @param array $data
101 | * Data array.
102 | * @param array $fields
103 | * Fields array.
104 | * @param string $base_field_name
105 | * Base field name.
106 | */
107 | function _elasticsearch_connector_views_handle_fields($base_table, &$data, $fields, $base_field_name = '') {
108 | if (!empty($fields)) {
109 | foreach ($fields as $field_name => $field) {
110 | // TODO: Restrict some fields if needed.
111 | // TODO: Handle boolean.
112 | // TODO: Handle the cases with analyzed and not analyzed.
113 | if (empty($field['type']) && isset($field['properties'])) {
114 | $field_type = 'object';
115 | }
116 | else {
117 | $field_type = $field['type'];
118 | }
119 |
120 | $filter_handler = 'elasticsearch_connector_views_standard';
121 | $field_handler = 'elasticsearch_connector_views_standard';
122 | $set = TRUE;
123 | switch ($field_type) {
124 | case 'object':
125 | if (!empty($field['properties'])) {
126 | _elasticsearch_connector_views_handle_fields($base_table, $data, $field['properties'], $base_field_name . $field_name . '.');
127 | }
128 | $set = FALSE;
129 | break;
130 |
131 | case 'date':
132 | $filter_handler = 'elasticsearch_connector_views_date';
133 | $field_handler = 'elasticsearch_connector_views_date';
134 | break;
135 |
136 | case 'boolean':
137 | $filter_handler = 'elasticsearch_connector_views_boolean';
138 | $field_handler = 'elasticsearch_connector_views_boolean';
139 | break;
140 |
141 | case 'text':
142 | case 'string':
143 | // TODO: Handle the analyser and non_analyzed fields.
144 | // TODO: For analysed fields we need to do fulltext search.
145 | if (\Drupal::moduleHandler()
146 | ->moduleExists('views_autocomplete_filters')
147 | ) {
148 | // TODO: Handle autocomplete.
149 | //$filter_handler = 'elasticsearch_connector_views_handler_filter_string_autocomplete';
150 | }
151 | else {
152 | $field_handler = 'elasticsearch_connector_views_markup';
153 | $filter_handler = 'elasticsearch_connector_views_string';
154 | }
155 | break;
156 |
157 | // Handle numeric filter type.
158 | case 'integer':
159 | case 'long':
160 | case 'float':
161 | case 'double':
162 | $filter_handler = 'elasticsearch_connector_views_numeric';
163 | $field_handler = 'elasticsearch_connector_views_numeric';
164 | break;
165 | }
166 |
167 | if ($set) {
168 | $data[$base_table][$base_field_name . $field_name] = array(
169 | 'title' => $base_field_name . $field_name,
170 | 'help' => $base_field_name . $field_name,
171 | 'field' => array(
172 | 'id' => $field_handler,
173 | 'click sortable' => TRUE,
174 | ),
175 | 'filter' => array(
176 | 'id' => $filter_handler,
177 | ),
178 | 'sort' => array(
179 | 'id' => 'standard',
180 | ),
181 | // TODO: Handle the argument class.
182 | 'argument' => array(
183 | 'id' => 'standard',
184 | ),
185 | );
186 | }
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/modules/elasticsearch_connector_views/src/Plugin/views/ElasticsearchViewsHandlerTrait.php:
--------------------------------------------------------------------------------
1 | definition['entity_type'])) {
33 | return $this->definition['entity_type'];
34 | }
35 | return parent::getEntityType();
36 | }
37 |
38 | /**
39 | * Returns the active search index.
40 | *
41 | * @return string
42 | * The index to use with this filter, or NULL if none could be
43 | * loaded.
44 | */
45 | protected function getIndex() {
46 | // TODO: Implement.
47 | return NULL;
48 | }
49 |
50 | /**
51 | * Retrieves the query plugin.
52 | */
53 | public function getQuery() {
54 | return NULL;
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/modules/elasticsearch_connector_views/src/Plugin/views/field/ElasticsearchViewsBoolean.php:
--------------------------------------------------------------------------------
1 | setEntityDisplayRepository($container->get('entity_display.repository'));
38 |
39 | return $field;
40 | }
41 |
42 | /**
43 | * Retrieves the entity display repository.
44 | *
45 | * @return \Drupal\Core\Entity\EntityDisplayRepositoryInterface
46 | * The entity entity display repository.
47 | */
48 | public function getEntityDisplayRepository() {
49 | return $this->entityDisplayRepository ?: \Drupal::service('entity_display.repository');
50 | }
51 |
52 | /**
53 | * Sets the entity display repository.
54 | *
55 | * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
56 | * The new entity display repository.
57 | *
58 | * @return $this
59 | */
60 | public function setEntityDisplayRepository(EntityDisplayRepositoryInterface $entity_display_repository) {
61 | $this->entityDisplayRepository = $entity_display_repository;
62 | return $this;
63 | }
64 |
65 | /**
66 | * {@inheritdoc}
67 | */
68 | public function defineOptions() {
69 | $options = parent::defineOptions();
70 |
71 | $options['display_methods'] = array('default' => array());
72 |
73 | return $options;
74 | }
75 |
76 | /**
77 | * {@inheritdoc}
78 | */
79 | public function buildOptionsForm(&$form, FormStateInterface $form_state) {
80 | parent::buildOptionsForm($form, $form_state);
81 |
82 | $entity_type_id = $this->getTargetEntityTypeId();
83 | $view_modes = array();
84 | $bundles = array();
85 | if ($entity_type_id) {
86 | $bundles = $this->getEntityManager()->getBundleInfo($entity_type_id);
87 | // In case the field definition specifies the bundles to expect, restrict
88 | // the displayed bundles to those.
89 | $settings = $this->getFieldDefinition()->getSettings();
90 | if (!empty($settings['handler_settings']['target_bundles'])) {
91 | $bundles = array_intersect_key($bundles, $settings['handler_settings']['target_bundles']);
92 | }
93 | foreach ($bundles as $bundle => $info) {
94 | $view_modes[$bundle] = $this->getEntityDisplayRepository()
95 | ->getViewModeOptionsByBundle($entity_type_id, $bundle);
96 | }
97 | }
98 |
99 | foreach ($bundles as $bundle => $info) {
100 | $args['@bundle'] = $info['label'];
101 | $form['display_methods'][$bundle]['display_method'] = array(
102 | '#type' => 'select',
103 | '#title' => $this->t('Display for "@bundle" bundle', $args),
104 | '#options' => array(
105 | '' => $this->t('Hide'),
106 | 'id' => $this->t('Raw ID'),
107 | 'label' => $this->t('Only label'),
108 | ),
109 | '#default_value' => 'label',
110 | );
111 | if (isset($this->options['display_methods'][$bundle]['display_method'])) {
112 | $form['display_methods'][$bundle]['display_method']['#default_value'] = $this->options['display_methods'][$bundle]['display_method'];
113 | }
114 | if (!empty($view_modes[$bundle])) {
115 | $form['display_methods'][$bundle]['display_method']['#options']['view_mode'] = $this->t('Entity view');
116 | if (count($view_modes[$bundle]) > 1) {
117 | $form['display_methods'][$bundle]['view_mode'] = array(
118 | '#type' => 'select',
119 | '#title' => $this->t('View mode for "@bundle" bundle', $args),
120 | '#options' => $view_modes[$bundle],
121 | '#states' => array(
122 | 'visible' => array(
123 | ':input[name="options[display_methods][' . $bundle . '][display_method]"]' => array(
124 | 'value' => 'view_mode',
125 | ),
126 | ),
127 | ),
128 | );
129 | if (isset($this->options['display_methods'][$bundle]['view_mode'])) {
130 | $form['display_methods'][$bundle]['view_mode']['#default_value'] = $this->options['display_methods'][$bundle]['view_mode'];
131 | }
132 | }
133 | else {
134 | reset($view_modes[$bundle]);
135 | $form['display_methods'][$bundle]['view_mode'] = array(
136 | '#type' => 'value',
137 | '#value' => key($view_modes[$bundle]),
138 | );
139 | }
140 | }
141 | if (count($bundles) == 1) {
142 | $form['display_methods'][$bundle]['display_method']['#title'] = $this->t('Display method');
143 | if (!empty($form['display_methods'][$bundle]['view_mode'])) {
144 | $form['display_methods'][$bundle]['view_mode']['#title'] = $this->t('View mode');
145 | }
146 | }
147 | }
148 |
149 | $form['link_to_item']['#description'] .= ' ' . $this->t('This will only take effect for entities for which only the entity label is displayed.');
150 | $form['link_to_item']['#weight'] = 5;
151 | }
152 |
153 | /**
154 | * Return the entity type ID of the entity this field handler should display.
155 | *
156 | * @return string|null
157 | * The entity type ID, or NULL if it could not be found.
158 | */
159 | public function getTargetEntityTypeId() {
160 | $field_definition = $this->getFieldDefinition();
161 | if ($field_definition->getType() === 'field_item:comment') {
162 | return 'comment';
163 | }
164 | return $field_definition->getSetting('target_type');
165 | }
166 |
167 | /**
168 | * {@inheritdoc}
169 | */
170 | public function query() {
171 | $this->addRetrievedProperty($this->getCombinedPropertyPath());
172 | }
173 |
174 | /**
175 | * {@inheritdoc}
176 | */
177 | public function preRender(&$values) {
178 | parent::preRender($values);
179 |
180 | // The parent method will just have loaded the entity IDs. We now multi-load
181 | // the actual objects.
182 | $property_path = $this->getCombinedPropertyPath();
183 | foreach ($values as $i => $row) {
184 | if (!empty($row->{$property_path})) {
185 | foreach ((array) $row->{$property_path} as $j => $value) {
186 | if (is_scalar($value)) {
187 | $to_load[$value][] = array($i, $j);
188 | }
189 | }
190 | }
191 | }
192 |
193 | if (empty($to_load)) {
194 | return;
195 | }
196 |
197 | $entities = $this->getEntityManager()
198 | ->getStorage($this->getTargetEntityTypeId())
199 | ->loadMultiple(array_keys($to_load));
200 | $account = $this->getQuery()->getAccessAccount();
201 | foreach ($entities as $id => $entity) {
202 | foreach ($to_load[$id] as list($i, $j)) {
203 | if ($entity->access('view', $account)) {
204 | $values[$i]->{$property_path}[$j] = $entity;
205 | }
206 | }
207 | }
208 | }
209 |
210 | /**
211 | * {@inheritdoc}
212 | */
213 | public function render_item($count, $item) {
214 | if (is_array($item['value'])) {
215 | return $this->getRenderer()->render($item['value']);
216 | }
217 | return parent::render_item($count, $item);
218 | }
219 |
220 | /**
221 | * {@inheritdoc}
222 | */
223 | public function getItems(ResultRow $values) {
224 | $property_path = $this->getCombinedPropertyPath();
225 | if (!empty($values->{$property_path})) {
226 | $items = array();
227 | foreach ((array) $values->{$property_path} as $value) {
228 | if ($value instanceof EntityInterface) {
229 | $item = $this->getItem($value);
230 | if ($item) {
231 | $items[] = $item;
232 | }
233 | }
234 | }
235 | return $items;
236 | }
237 | return array();
238 | }
239 |
240 | /**
241 | * Creates an item for the given entity.
242 | *
243 | * @param \Drupal\Core\Entity\EntityInterface $entity
244 | * The entity.
245 | *
246 | * @return array|null
247 | * NULL if the entity should not be displayed. Otherwise, an associative
248 | * array with at least "value" set, to either a string or a render array,
249 | * and possibly also additional alter options.
250 | */
251 | protected function getItem(EntityInterface $entity) {
252 | $bundle = $entity->bundle();
253 | if (empty($this->options['display_methods'][$bundle]['display_method'])) {
254 | return NULL;
255 | }
256 |
257 | $display_method = $this->options['display_methods'][$bundle]['display_method'];
258 | if (in_array($display_method, array('id', 'label'))) {
259 | if ($display_method == 'label') {
260 | $item['value'] = $entity->label();
261 | }
262 | else {
263 | $item['value'] = $entity->id();
264 | }
265 |
266 | if ($this->options['link_to_item']) {
267 | $item['make_link'] = TRUE;
268 | $item['url'] = $entity->toUrl('canonical');
269 | }
270 |
271 | return $item;
272 | }
273 |
274 | $view_mode = $this->options['display_methods'][$bundle]['view_mode'];
275 | $build = $this->getEntityManager()
276 | ->getViewBuilder($entity->getEntityTypeId())
277 | ->view($entity, $view_mode);
278 | return array(
279 | 'value' => $build,
280 | );
281 | }
282 |
283 | }
284 |
--------------------------------------------------------------------------------
/modules/elasticsearch_connector_views/src/Plugin/views/field/ElasticsearchViewsEntityField.php:
--------------------------------------------------------------------------------
1 | definition['fallback_handler']) ? $this->definition['fallback_handler'] : 'elasticsearch_connector_views_standard';
47 | $this->fallbackHandler = Views::handlerManager('field')
48 | ->getHandler($options, $fallback_handler_id);
49 | $options += array('fallback_options' => array());
50 | $fallback_options = $options['fallback_options'] + $options;
51 | $this->fallbackHandler->init($view, $display, $fallback_options);
52 |
53 | parent::init($view, $display, $options);
54 | }
55 |
56 | /**
57 | * {@inheritdoc}
58 | */
59 | public function query($use_groupby = false) {
60 | // If we're not using Field API field rendering, just use the query()
61 | // implementation of the fallback handler.
62 | if (!$this->options['field_rendering']) {
63 | $this->fallbackHandler->query();
64 | return;
65 | }
66 |
67 | // If we do use Field API rendering, we need the entity object for the
68 | // parent property.
69 | $parent_path = $this->getParentPath();
70 | $property_path = $parent_path ? "$parent_path:_object" : '_object';
71 | $combined_property_path = Utility::createCombinedId($this->getDatasourceId(), $property_path);
72 | $this->addRetrievedProperty($combined_property_path);
73 | }
74 |
75 | /**
76 | * Retrieves the property path of the parent property.
77 | *
78 | * @return string|null
79 | * The property path of the parent property.
80 | */
81 | protected function getParentPath() {
82 | if (!isset($this->parentPath)) {
83 | $combined_property_path = $this->getCombinedPropertyPath();
84 | list(, $property_path) = Utility::splitCombinedId($combined_property_path);
85 | list($this->parentPath) = Utility::splitPropertyPath($property_path);
86 | }
87 |
88 | return $this->parentPath;
89 | }
90 |
91 | /**
92 | * {@inheritdoc}
93 | */
94 | public function defineOptions() {
95 | $options = parent::defineOptions();
96 |
97 | $options['field_rendering'] = array('default' => TRUE);
98 | $options['fallback_handler'] = array('default' => $this->fallbackHandler->getPluginId());
99 | $options['fallback_options'] = array('contains' => $this->fallbackHandler->defineOptions());
100 |
101 | return $options;
102 | }
103 |
104 | /**
105 | * {@inheritdoc}
106 | */
107 | public function buildOptionsForm(&$form, FormStateInterface $form_state) {
108 | $form['field_rendering'] = array(
109 | '#type' => 'checkbox',
110 | '#title' => $this->t('Use entity field rendering'),
111 | '#description' => $this->t("If checked, Drupal's built-in field rendering mechanism will be used for rendering this field's values, which requires the entity to be loaded. If unchecked, a type-specific, entity-independent rendering mechanism will be used."),
112 | '#default_value' => $this->options['field_rendering'],
113 | );
114 |
115 | // Wrap the (immediate) parent options in their own field set, to clean up
116 | // the UI when (un)checking the above checkbox.
117 | $form['parent_options'] = array(
118 | '#type' => 'fieldset',
119 | '#title' => $this->t('Render settings'),
120 | '#states' => array(
121 | 'visible' => array(
122 | ':input[name="options[field_rendering]"]' => array('checked' => TRUE),
123 | ),
124 | ),
125 | );
126 |
127 | // Include the parent options form and move all fields that were added by
128 | // our direct parent (\Drupal\views\Plugin\views\field\Field) to the
129 | // "parent_options" fieldset.
130 | parent::buildOptionsForm($form, $form_state);
131 | $parent_keys = array(
132 | 'multiple_field_settings',
133 | 'click_sort_column',
134 | 'type',
135 | 'field_api_classes',
136 | 'settings',
137 | );
138 | foreach ($parent_keys as $key) {
139 | if (!empty($form[$key])) {
140 | $form[$key]['#fieldset'] = 'parent_options';
141 | }
142 | }
143 | // The Core boolean formatter hard-codes the field name to "field_boolean".
144 | // This breaks the parent class's call of rewriteStatesSelector() for fixing
145 | // "#states". We therefore apply that behavior again here.
146 | if (!empty($form['settings'])) {
147 | FormHelper::rewriteStatesSelector($form['settings'], "fields[field_boolean][settings_edit_form]", 'options');
148 | }
149 |
150 | // Get the options form for the fallback handler.
151 | $fallback_form = array();
152 | $this->fallbackHandler->buildOptionsForm($fallback_form, $form_state);
153 | // Remove all fields from FieldPluginBase from the fallback form, but leave
154 | // those in that were only added by our immediate parent,
155 | // \Drupal\views\Plugin\views\field\Field. (E.g., the "type" option is
156 | // especially prone to conflicts here.) The others come from the plugin base
157 | // classes and will be identical, so it would be confusing to include them
158 | // twice.
159 | $parent_keys[] = '#pre_render';
160 | $remove_from_fallback = array_diff_key($form, array_flip($parent_keys));
161 | $fallback_form = array_diff_key($fallback_form, $remove_from_fallback);
162 | // Fix the "#states" selectors in the fallback form, and put an additional
163 | // "#states" directive on it to only be visible for the corresponding
164 | // "field_rendering" setting.
165 | if ($fallback_form) {
166 | FormHelper::rewriteStatesSelector($fallback_form, '"options[', '"options[fallback_options][');
167 | $form['fallback_options'] = $fallback_form;
168 | $form['fallback_options']['#type'] = 'fieldset';
169 | $form['fallback_options']['#title'] = $this->t('Render settings');
170 | $form['fallback_options']['#states']['visible'][':input[name="options[field_rendering]"]'] = array('checked' => FALSE);
171 | }
172 | }
173 |
174 | /**
175 | * {@inheritdoc}
176 | */
177 | public function preRender(&$values) {
178 | if ($this->options['field_rendering']) {
179 | parent::preRender($values);
180 | }
181 | else {
182 | $this->fallbackHandler->preRender($values);
183 | }
184 | }
185 |
186 | /**
187 | * {@inheritdoc}
188 | */
189 | public function render(ResultRow $values) {
190 | if (!$this->options['field_rendering']) {
191 | return $this->fallbackHandler->render($values);
192 | }
193 | return parent::render($values);
194 | }
195 |
196 | /**
197 | * {@inheritdoc}
198 | */
199 | public function render_item($count, $item) {
200 | if (!$this->options['field_rendering']) {
201 | if ($this->fallbackHandler instanceof MultiItemsFieldHandlerInterface) {
202 | return $this->fallbackHandler->render_item($count, $item);
203 | }
204 | return '';
205 | }
206 | return parent::render_item($count, $item);
207 | }
208 |
209 | /**
210 | * {@inheritdoc}
211 | */
212 | protected function getEntityFieldRenderer() {
213 | if (!isset($this->entityFieldRenderer)) {
214 | // This can be invoked during field handler initialization in which case
215 | // view fields are not set yet.
216 | if (!empty($this->view->field)) {
217 | foreach ($this->view->field as $field) {
218 | // An entity field renderer can handle only a single relationship.
219 | if (isset($field->entityFieldRenderer)) {
220 | if ($field->entityFieldRenderer instanceof EntityFieldRenderer && $field->entityFieldRenderer->compatibleWithField($this)) {
221 | $this->entityFieldRenderer = $field->entityFieldRenderer;
222 | break;
223 | }
224 | }
225 | }
226 | }
227 | if (!isset($this->entityFieldRenderer)) {
228 | $entity_type = $this->entityManager->getDefinition($this->getEntityType());
229 | $this->entityFieldRenderer = new EntityFieldRenderer($this->view, $this->relationship, $this->languageManager, $entity_type, $this->entityManager);
230 | $this->entityFieldRenderer->setDatasourceId($this->getDatasourceId());
231 | }
232 | }
233 |
234 | return $this->entityFieldRenderer;
235 | }
236 |
237 | /**
238 | * {@inheritdoc}
239 | */
240 | public function getItems(ResultRow $values) {
241 | return array();
242 | }
243 |
244 | /**
245 | * {@inheritdoc}
246 | */
247 | public function renderItems($items) {
248 | if (!$this->options['field_rendering']) {
249 | if ($this->fallbackHandler instanceof MultiItemsFieldHandlerInterface) {
250 | return $this->fallbackHandler->renderItems($items);
251 | }
252 | return '';
253 | }
254 |
255 | return parent::renderItems($items);
256 | }
257 |
258 | }
259 |
--------------------------------------------------------------------------------
/modules/elasticsearch_connector_views/src/Plugin/views/field/ElasticsearchViewsFieldTrait.php:
--------------------------------------------------------------------------------
1 | definition['format'])) {
23 | $this->definition['format'] = filter_default_format();
24 | }
25 | parent::init($view, $display, $options);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/modules/elasticsearch_connector_views/src/Plugin/views/field/ElasticsearchViewsNumeric.php:
--------------------------------------------------------------------------------
1 | options['fields'];
26 | if (!empty($this->value[0])) {
27 | foreach ($fields as $field) {
28 | $this->query->where['conditions'][$field] = $this->value[0];
29 | }
30 | }
31 | }
32 |
33 |
34 |
35 | /**
36 | * {@inheritdoc}
37 | */
38 | public function buildOptionsForm(&$form, FormStateInterface $form_state) {
39 | parent::buildOptionsForm($form, $form_state);
40 |
41 | $fields = $this->getFulltextFields();
42 | if (!empty($fields)) {
43 | $form['fields'] = array(
44 | '#type' => 'select',
45 | '#title' => t('Searched fields'),
46 | '#description' => t('Select the fields that will be searched.'),
47 | '#options' => $fields,
48 | '#size' => min(4, count($fields)),
49 | '#multiple' => TRUE,
50 | '#default_value' => $this->options['fields'],
51 | '#required' => TRUE,
52 | );
53 | }
54 | else {
55 | $form['fields'] = array(
56 | '#type' => 'value',
57 | '#value' => array(),
58 | );
59 | }
60 |
61 | if (isset($form['expose'])) {
62 | $form['expose']['#weight'] = -5;
63 | }
64 |
65 | }
66 |
67 | /**
68 | * Choose fulltext fields from ElasticSearch mapping if they are
69 | * type string and are analyzed (there is no index => not_analyzed
70 | * in the mapping)
71 | *
72 | * @return array fields
73 | */
74 | private function getFulltextFields() {
75 |
76 | $view_id = $this->view->storage->get('base_table');
77 | $data = Views::viewsData()->get($view_id);
78 |
79 | $index = $data['table']['base']['index'];
80 | $type = (is_array($data['table']['base']['type'])) ? implode(',', $data['table']['base']['type']) : $data['table']['base']['type'];
81 |
82 | $cluster_id = $data['table']['base']['cluster_id'];
83 |
84 | /** @var \Drupal\elasticsearch_connector\Entity\Cluster $elasticsearchCluster */
85 | $elasticsearchCluster = \Drupal::service('entity.manager')->getStorage('elasticsearch_cluster')->load($cluster_id);
86 | /** @var \Drupal\elasticsearch_connector\ElasticSearch\ClientManagerInterface $clientManager */
87 | $clientManager = \Drupal::service('elasticsearch_connector.client_manager');
88 | $client = $clientManager->getClientForCluster($elasticsearchCluster);
89 |
90 | $params = array(
91 | 'index' => $index,
92 | 'type' => $type,
93 | );
94 | $mapping = $client->indices()->getMapping($params);
95 |
96 | $fulltext_fields = array_keys(array_filter($mapping[$index]['mappings'][$type]['properties'], function($v) {
97 | return $v['type'] == 'text' && (!isset($v['index']) || $v['index'] != 'not_analyzed');
98 | }));
99 |
100 | return array_combine($fulltext_fields, $fulltext_fields);
101 | }
102 |
103 |
104 | /**
105 | * Provide a simple textfield for equality
106 | */
107 | protected function valueForm(&$form, FormStateInterface $form_state) {
108 | // We have to make some choices when creating this as an exposed
109 | // filter form. For example, if the operator is locked and thus
110 | // not rendered, we can't render dependencies; instead we only
111 | // render the form items we need.
112 | $which = 'all';
113 | if (!empty($form['operator'])) {
114 | $source = ':input[name="options[operator]"]';
115 | }
116 | if ($exposed = $form_state->get('exposed')) {
117 | $identifier = $this->options['expose']['identifier'];
118 |
119 | if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
120 | // Exposed and locked.
121 | $which = in_array($this->operator, $this->operatorValues(1)) ? 'value' : 'none';
122 | }
123 | else {
124 | $source = ':input[name="' . $this->options['expose']['operator_id'] . '"]';
125 | }
126 | }
127 |
128 | if ($which == 'all' || $which == 'value') {
129 | $form['value'] = array(
130 | '#type' => 'textfield',
131 | '#title' => $this->t('Value'),
132 | '#size' => 30,
133 | '#default_value' => $this->value,
134 | );
135 | $user_input = $form_state->getUserInput();
136 | if ($exposed && !isset($user_input[$identifier])) {
137 | $user_input[$identifier] = $this->value;
138 | $form_state->setUserInput($user_input);
139 | }
140 |
141 | if ($which == 'all') {
142 | // Setup #states for all operators with one value.
143 | foreach ($this->operatorValues(1) as $operator) {
144 | $form['value']['#states']['visible'][] = array(
145 | $source => array('value' => $operator),
146 | );
147 | }
148 | }
149 | }
150 |
151 | if (!isset($form['value'])) {
152 | // Ensure there is something in the 'value'.
153 | $form['value'] = array(
154 | '#type' => 'value',
155 | '#value' => NULL,
156 | );
157 | }
158 | }
159 |
160 | /**
161 | * Helper function to define Options.
162 | */
163 | protected function defineOptions() {
164 | $options = parent::defineOptions();
165 |
166 | $options['expose']['contains']['required'] = array('default' => FALSE);
167 |
168 | $options['min_length']['default'] = '';
169 | $options['fields']['default'] = [];
170 |
171 | return $options;
172 | }
173 |
174 | /**
175 | * Helper function to build Admin Summary.
176 | */
177 | public function adminSummary() {
178 | if ($this->isAGroup()) {
179 | return $this->t('grouped');
180 | }
181 | if (!empty($this->options['exposed'])) {
182 | return $this->t('exposed');
183 | }
184 |
185 | $options = $this->operatorOptions('short');
186 | $output = '';
187 | if (!empty($options[$this->operator])) {
188 | $output = $options[$this->operator];
189 | }
190 | if (in_array($this->operator, $this->operatorValues(1))) {
191 | $output .= ' ' . $this->value;
192 | }
193 | return $output;
194 | }
195 |
196 | /**
197 | * Helper function to build operator values.
198 | */
199 | protected function operatorValues($values = 1) {
200 | $options = array();
201 | foreach ($this->operators() as $id => $info) {
202 | if (isset($info['values']) && $info['values'] == $values) {
203 | $options[] = $id;
204 | }
205 | }
206 |
207 | return $options;
208 | }
209 |
210 | /**
211 | * Build strings from the operators() for 'select' options
212 | */
213 | public function operatorOptions($which = 'title') {
214 | $options = array();
215 | foreach ($this->operators() as $id => $info) {
216 | $options[$id] = $info[$which];
217 | }
218 |
219 | return $options;
220 | }
221 |
222 | /**
223 | * Helper function to define opertators.
224 | */
225 | public function operators() {
226 | $operators = array(
227 | 'word' => array(
228 | 'title' => $this->t('Contains any word'),
229 | 'short' => $this->t('has word'),
230 | 'method' => 'opContainsWord',
231 | 'values' => 1,
232 | ),
233 | );
234 | return $operators;
235 | }
236 |
237 |
238 |
239 | }
240 |
--------------------------------------------------------------------------------
/modules/elasticsearch_connector_views/src/Plugin/views/filter/ElasticsearchViewsNumericFilter.php:
--------------------------------------------------------------------------------
1 | get('exposed')) {
31 | $identifier = $this->options['expose']['identifier'];
32 |
33 | if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
34 | // Exposed and locked.
35 | $which = in_array($this->operator, $this->operatorValues(1)) ? 'value' : 'none';
36 | }
37 | else {
38 | $source = ':input[name="' . $this->options['expose']['operator_id'] . '"]';
39 | }
40 | }
41 |
42 | if ($which == 'all' || $which == 'value') {
43 | $form['value'] = array(
44 | '#type' => 'textfield',
45 | '#title' => $this->t('Value'),
46 | '#size' => 30,
47 | '#default_value' => $this->value,
48 | );
49 | $user_input = $form_state->getUserInput();
50 | if ($exposed && !isset($user_input[$identifier])) {
51 | $user_input[$identifier] = $this->value;
52 | $form_state->setUserInput($user_input);
53 | }
54 |
55 | if ($which == 'all') {
56 | // Setup #states for all operators with one value.
57 | foreach ($this->operatorValues(1) as $operator) {
58 | $form['value']['#states']['visible'][] = array(
59 | $source => array('value' => $operator),
60 | );
61 | }
62 | }
63 | }
64 |
65 | if (!isset($form['value'])) {
66 | // Ensure there is something in the 'value'.
67 | $form['value'] = array(
68 | '#type' => 'value',
69 | '#value' => NULL,
70 | );
71 | }
72 | }
73 |
74 | /**
75 | * Helper function to define Options.
76 | */
77 | protected function defineOptions() {
78 | $options = parent::defineOptions();
79 |
80 | $options['expose']['contains']['required'] = array('default' => FALSE);
81 |
82 | return $options;
83 | }
84 |
85 | /**
86 | * Helper function to build Admin Summary.
87 | */
88 | public function adminSummary() {
89 | if ($this->isAGroup()) {
90 | return $this->t('grouped');
91 | }
92 | if (!empty($this->options['exposed'])) {
93 | return $this->t('exposed');
94 | }
95 |
96 | $options = $this->operatorOptions('short');
97 | $output = '';
98 | if (!empty($options[$this->operator])) {
99 | $output = $options[$this->operator];
100 | }
101 | if (in_array($this->operator, $this->operatorValues(1))) {
102 | $output .= ' ' . $this->value;
103 | }
104 | return $output;
105 | }
106 |
107 | /**
108 | * Helper function to build operator values.
109 | */
110 | protected function operatorValues($values = 1) {
111 | $options = array();
112 | foreach ($this->operators() as $id => $info) {
113 | if (isset($info['values']) && $info['values'] == $values) {
114 | $options[] = $id;
115 | }
116 | }
117 |
118 | return $options;
119 | }
120 |
121 | /**
122 | * Build strings from the operators() for 'select' options
123 | */
124 | public function operatorOptions($which = 'title') {
125 | $options = array();
126 | foreach ($this->operators() as $id => $info) {
127 | $options[$id] = $info[$which];
128 | }
129 |
130 | return $options;
131 | }
132 |
133 | /**
134 | * Helper function to define opertators.
135 | */
136 | public function operators() {
137 | $operators = array(
138 | '=' => array(
139 | 'title' => $this->t('Is equal to'),
140 | 'short' => $this->t('='),
141 | 'method' => 'opEqual',
142 | 'values' => 1,
143 | ),
144 | '!=' => array(
145 | 'title' => $this->t('Is not equal to'),
146 | 'short' => $this->t('!='),
147 | 'method' => 'opEqual',
148 | 'values' => 1,
149 | ),
150 | 'word' => array(
151 | 'title' => $this->t('Contains any word'),
152 | 'short' => $this->t('has word'),
153 | 'method' => 'opContainsWord',
154 | 'values' => 1,
155 | ),
156 | 'allwords' => array(
157 | 'title' => $this->t('Contains all words'),
158 | 'short' => $this->t('has all'),
159 | 'method' => 'opContainsWord',
160 | 'values' => 1,
161 | ),
162 | 'starts' => array(
163 | 'title' => $this->t('Starts with'),
164 | 'short' => $this->t('begins'),
165 | 'method' => 'opStartsWith',
166 | 'values' => 1,
167 | ),
168 | );
169 | // If the definition allows for the empty operator, add it.
170 | if (!empty($this->definition['allow empty'])) {
171 | $operators += array(
172 | 'empty' => array(
173 | 'title' => $this->t('Is empty (NULL)'),
174 | 'method' => 'opEmpty',
175 | 'short' => $this->t('empty'),
176 | 'values' => 0,
177 | ),
178 | 'not empty' => array(
179 | 'title' => $this->t('Is not empty (NOT NULL)'),
180 | 'method' => 'opEmpty',
181 | 'short' => $this->t('not empty'),
182 | 'values' => 0,
183 | ),
184 | );
185 | }
186 |
187 | return $operators;
188 | }
189 |
190 | /**
191 | * Helper function to query.
192 | */
193 | public function query() {
194 | if (!empty($this->value[0])) {
195 | $this->query->where['conditions'][$this->realField] = $this->value[0];
196 | }
197 | }
198 |
199 | }
200 |
--------------------------------------------------------------------------------
/modules/elasticsearch_connector_views/src/Plugin/views/join/ElasticsearchViewsJoin.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
14 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | ./tests/TestSuites/UnitTestSuite.php
37 |
38 |
39 | ./tests/TestSuites/KernelTestSuite.php
40 |
41 |
42 | ./tests/TestSuites/FunctionalTestSuite.php
43 |
44 |
45 | ./tests/TestSuites/UnitTestSuite.php
46 | ./tests/TestSuites/KernelTestSuite.php
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | ../modules/elasticsearch_connector
57 |
58 | ../modules/elasticsearch_connector/tests
59 | ../modules/elasticsearch_connector/test_modules
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/ClusterManager.php:
--------------------------------------------------------------------------------
1 | state = $state;
35 | $this->entityTypeManager = $entity_type_manager;
36 | }
37 |
38 | /**
39 | * Get the default (cluster) used by Elasticsearch.
40 | *
41 | * @return string
42 | * The cluster identifier.
43 | */
44 | public function getDefaultCluster() {
45 | return $this->state->get('elasticsearch_connector_get_default_connector',
46 | '');
47 | }
48 |
49 | /**
50 | * Set the default (cluster) used by Elasticsearch.
51 | *
52 | * @param string $cluster_id
53 | * The new cluster identifier.
54 | */
55 | public function setDefaultCluster($cluster_id) {
56 | $this->state->set(
57 | 'elasticsearch_connector_get_default_connector',
58 | $cluster_id
59 | );
60 | }
61 |
62 | /**
63 | * Load all clusters.
64 | *
65 | * @param bool $include_inactive
66 | *
67 | * @return \Drupal\elasticsearch_connector\Entity\Cluster[]
68 | */
69 | public function loadAllClusters($include_inactive = TRUE) {
70 | $clusters = $this->entityTypeManager->getStorage('elasticsearch_cluster')->loadMultiple();
71 | foreach ($clusters as $cluster) {
72 | if (!$include_inactive && !$cluster->status) {
73 | unset($clusters[$cluster->cluster_id]);
74 | }
75 | }
76 |
77 | return $clusters;
78 | }
79 |
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/Controller/ClusterListBuilder.php:
--------------------------------------------------------------------------------
1 | indexStorage = $index_storage;
55 | $this->clusterStorage = $cluster_storage;
56 | $this->clientManager = $client_manager;
57 | }
58 |
59 | /**
60 | * {@inheritdoc}
61 | */
62 | public static function createInstance(
63 | ContainerInterface $container,
64 | EntityTypeInterface $entity_type
65 | ) {
66 | return new static(
67 | $entity_type,
68 | $container->get('entity_type.manager')->getStorage($entity_type->id()),
69 | $container->get('entity_type.manager')->getStorage('elasticsearch_index'),
70 | $container->get('entity_type.manager')->getStorage('elasticsearch_cluster'),
71 | $container->get('elasticsearch_connector.client_manager')
72 | );
73 | }
74 |
75 | /**
76 | * Group Elasticsearch indices under their respective clusters.
77 | *
78 | * @return array
79 | * Associative array with the following keys:
80 | * - clusters: Array of cluster groups keyed by cluster id. Each item is
81 | * itself an array with the cluster and any indices as values.
82 | * - lone_indexes: Array of indices without a cluster.
83 | */
84 | public function group() {
85 | /** @var \Drupal\elasticsearch_connector\Entity\Cluster[] $clusters */
86 | $clusters = $this->storage->loadMultiple();
87 | /** @var \Drupal\elasticsearch_connector\Entity\Index[] $indices */
88 | $indices = $this->indexStorage->loadMultiple();
89 |
90 | $cluster_groups = [];
91 | $lone_indices = [];
92 | foreach ($clusters as $cluster) {
93 | $cluster_group = [
94 | 'cluster.' . $cluster->cluster_id => $cluster,
95 | ];
96 |
97 | foreach ($indices as $index) {
98 | if ($index->server == $cluster->cluster_id) {
99 | $cluster_group['index.' . $index->index_id] = $index;
100 | }
101 | elseif ($index->server == NULL) {
102 | $lone_indices['index.' . $index->index_id] = $index;
103 | }
104 | }
105 |
106 | $cluster_groups['cluster.' . $cluster->cluster_id] = $cluster_group;
107 | }
108 |
109 | return [
110 | 'clusters' => $cluster_groups,
111 | 'lone_indexes' => $lone_indices,
112 | ];
113 | }
114 |
115 | /**
116 | * {@inheritdoc}
117 | */
118 | public function buildHeader() {
119 | return [
120 | 'type' => $this->t('Type'),
121 | 'title' => $this->t('Name'),
122 | 'machine_name' => $this->t('Machine Name'),
123 | 'status' => $this->t('Status'),
124 | 'cluster_status' => $this->t('Cluster Status'),
125 | ] + parent::buildHeader();
126 | }
127 |
128 | /**
129 | * {@inheritdoc}
130 | */
131 | public function buildRow(EntityInterface $entity) {
132 | if ($entity instanceof Cluster) {
133 | $client_connector = $this->clientManager->getClientForCluster($entity);
134 | }
135 | elseif ($entity instanceof Index) {
136 | $cluster = $this->clusterStorage->load($entity->server);
137 | $client_connector = $this->clientManager->getClientForCluster($cluster);
138 | }
139 | else {
140 | throw new NotFoundHttpException();
141 | }
142 |
143 | $row = parent::buildRow($entity);
144 | $result = [];
145 | $status = NULL;
146 | if (isset($entity->cluster_id)) {
147 | $cluster = $this->clusterStorage->load($entity->cluster_id);
148 |
149 | if ($client_connector->isClusterOk()) {
150 | $cluster_health = $client_connector->cluster()->health();
151 | $version_number = $client_connector->getServerVersion();
152 | $status = $cluster_health['status'];
153 | }
154 | else {
155 | $status = $this->t('Not available');
156 | $version_number = $this->t('Unknown version');
157 | }
158 | $result = [
159 | 'data' => [
160 | 'type' => [
161 | 'data' => $this->t('Cluster'),
162 | ],
163 | 'title' => [
164 | 'data' => [
165 | '#type' => 'link',
166 | '#title' => $entity->label() . ' (' . $version_number . ')',
167 | '#url' => new Url('entity.elasticsearch_cluster.edit_form', ['elasticsearch_cluster' => $entity->id()]),
168 | ],
169 | ],
170 | 'machine_name' => [
171 | 'data' => $entity->id(),
172 | ],
173 | 'status' => [
174 | 'data' => $cluster->status ? 'Active' : 'Inactive',
175 | ],
176 | 'clusterStatus' => [
177 | 'data' => $status,
178 | ],
179 | 'operations' => $row['operations'],
180 | ],
181 | 'title' => $this->t(
182 | 'Machine name: @name',
183 | ['@name' => $entity->id()]
184 | ),
185 | ];
186 | }
187 | elseif (isset($entity->index_id)) {
188 | $result = [
189 | 'data' => [
190 | 'type' => [
191 | 'data' => $this->t('Index'),
192 | 'class' => ['es-list-index'],
193 | ],
194 | 'title' => [
195 | 'data' => $entity->label(),
196 | ],
197 | 'machine_name' => [
198 | 'data' => $entity->id(),
199 | ],
200 | 'status' => [
201 | 'data' => '',
202 | ],
203 | 'clusterStatus' => [
204 | 'data' => '-',
205 | ],
206 | 'operations' => $row['operations'],
207 | ],
208 | 'title' => $this->t(
209 | 'Machine name: @name',
210 | ['@name' => $entity->id()]
211 | ),
212 | ];
213 | }
214 |
215 | return $result;
216 | }
217 |
218 | /**
219 | * {@inheritdoc}
220 | */
221 | public function getDefaultOperations(EntityInterface $entity) {
222 | $operations = [];
223 |
224 | if (isset($entity->cluster_id)) {
225 | $operations['info'] = [
226 | 'title' => $this->t('Info'),
227 | 'weight' => 19,
228 | 'url' => new Url('entity.elasticsearch_cluster.canonical', ['elasticsearch_cluster' => $entity->id()]),
229 | ];
230 | $operations['edit'] = [
231 | 'title' => $this->t('Edit'),
232 | 'weight' => 20,
233 | 'url' => new Url('entity.elasticsearch_cluster.edit_form', ['elasticsearch_cluster' => $entity->id()]),
234 | ];
235 | $operations['delete'] = [
236 | 'title' => $this->t('Delete'),
237 | 'weight' => 21,
238 | 'url' => new Url('entity.elasticsearch_cluster.delete_form', ['elasticsearch_cluster' => $entity->id()]),
239 | ];
240 | }
241 | elseif (isset($entity->index_id)) {
242 | $operations['delete'] = [
243 | 'title' => $this->t('Delete'),
244 | 'weight' => 20,
245 | 'url' => new Url('entity.elasticsearch_index.delete_form', ['elasticsearch_index' => $entity->id()]),
246 | ];
247 | }
248 |
249 | return $operations;
250 | }
251 |
252 | /**
253 | * {@inheritdoc}
254 | */
255 | public function render() {
256 | $entity_groups = $this->group();
257 |
258 | $rows = [];
259 | foreach ($entity_groups['clusters'] as $cluster_group) {
260 | /** @var \Drupal\elasticsearch_connector\Entity\Cluster|\Drupal\elasticsearch_connector\Entity\Index $entity */
261 | foreach ($cluster_group as $entity) {
262 | $rows[$entity->id()] = $this->buildRow($entity);
263 | }
264 | }
265 |
266 | $list['#type'] = 'container';
267 | $list['#attached']['library'][] = 'elasticsearch_connector/drupal.elasticsearch_connector.ec_index';
268 | $list['clusters'] = [
269 | '#type' => 'table',
270 | '#header' => $this->buildHeader(),
271 | '#rows' => $rows,
272 | '#empty' => $this->t(
273 | 'No clusters available. Add new cluster.',
274 | [
275 | '@link' => \Drupal::urlGenerator()->generate(
276 | 'entity.elasticsearch_cluster.add_form'
277 | ),
278 | ]
279 | ),
280 | ];
281 |
282 | return $list;
283 | }
284 |
285 | }
286 |
--------------------------------------------------------------------------------
/src/Controller/ElasticsearchController.php:
--------------------------------------------------------------------------------
1 | clientManager = $client_manager;
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | public static function create(ContainerInterface $container) {
36 | return new static (
37 | $container->get('elasticsearch_connector.client_manager')
38 | );
39 | }
40 |
41 | /**
42 | * Displays information about an Elasticsearch Cluster.
43 | *
44 | * @param \Drupal\elasticsearch_connector\Entity\Cluster $elasticsearch_cluster
45 | * An instance of Cluster.
46 | *
47 | * @return array
48 | * An array suitable for drupal_render().
49 | */
50 | public function page(Cluster $elasticsearch_cluster) {
51 | // Build the Search API index information.
52 | $render = [
53 | 'view' => [
54 | '#theme' => 'elasticsearch_cluster',
55 | '#cluster' => $elasticsearch_cluster,
56 | ],
57 | ];
58 | // Check if the cluster is enabled and can be written to.
59 | if ($elasticsearch_cluster->cluster_id) {
60 | $render['form'] = $this->formBuilder()->getForm(
61 | 'Drupal\elasticsearch_connector\Form\ClusterForm',
62 | $elasticsearch_cluster
63 | );
64 | }
65 |
66 | return $render;
67 | }
68 |
69 | /**
70 | * Page title callback for a cluster's "View" tab.
71 | *
72 | * @param \Drupal\elasticsearch_connector\Entity\Cluster $elasticsearch_cluster
73 | * The cluster that is displayed.
74 | *
75 | * @return string
76 | * The page title.
77 | */
78 | public function pageTitle(Cluster $elasticsearch_cluster) {
79 | // TODO: Check if we need string escaping.
80 | return $elasticsearch_cluster->label();
81 | }
82 |
83 | /**
84 | * Complete information about the Elasticsearch Client.
85 | *
86 | * @param \Drupal\elasticsearch_connector\Entity\Cluster $elasticsearch_cluster
87 | * Elasticsearch cluster.
88 | *
89 | * @return array
90 | * Render array.
91 | */
92 | public function getInfo(Cluster $elasticsearch_cluster) {
93 | // TODO: Get the statistics differently.
94 | $client_connector = $this->clientManager->getClientForCluster($elasticsearch_cluster);
95 |
96 | $node_rows = [];
97 | $cluster_statistics_rows = [];
98 | $cluster_health_rows = [];
99 |
100 | if ($client_connector->isClusterOk()) {
101 | // Nodes.
102 | $es_node_namespace = $client_connector->getNodesProperties();
103 | $node_stats = $es_node_namespace['stats'];
104 |
105 | $total_docs = 0;
106 | $total_size = 0;
107 | $node_rows = [];
108 | if (!empty($node_stats['nodes'])) {
109 | // TODO: Better format the results in order to build the
110 | // correct output.
111 | foreach ($node_stats['nodes'] as $node_id => $node_properties) {
112 | $row = [];
113 | $row[] = ['data' => $node_properties['name']];
114 | $row[] = ['data' => $node_properties['indices']['docs']['count']];
115 | $row[] = [
116 | 'data' => format_size(
117 | $node_properties['indices']['store']['size_in_bytes']
118 | ),
119 | ];
120 | $total_docs += $node_properties['indices']['docs']['count'];
121 | $total_size += $node_properties['indices']['store']['size_in_bytes'];
122 | $node_rows[] = $row;
123 | }
124 | }
125 |
126 | $cluster_status = $client_connector->getClusterInfo();
127 | $cluster_statistics_rows = [
128 | [
129 | [
130 | 'data' => $cluster_status['health']['number_of_nodes'] . ' ' . t(
131 | 'Nodes'
132 | ),
133 | ],
134 | [
135 | 'data' => $cluster_status['health']['active_shards'] + $cluster_status['health']['unassigned_shards'] . ' ' . t(
136 | 'Total Shards'
137 | ),
138 | ],
139 | [
140 | 'data' => $cluster_status['health']['active_shards'] . ' ' . t(
141 | 'Successful Shards'
142 | ),
143 | ],
144 | [
145 | 'data' => count(
146 | $cluster_status['state']['metadata']['indices']
147 | ) . ' ' . t('Indices'),
148 | ],
149 | ['data' => $total_docs . ' ' . t('Total Documents')],
150 | ['data' => format_size($total_size) . ' ' . t('Total Size')],
151 | ],
152 | ];
153 |
154 | $cluster_health_rows = [];
155 | $cluster_health_mapping = [
156 | 'cluster_name' => t('Cluster name'),
157 | 'status' => t('Status'),
158 | 'timed_out' => t('Time out'),
159 | 'number_of_nodes' => t('Number of nodes'),
160 | 'number_of_data_nodes' => t('Number of data nodes'),
161 | 'active_primary_shards' => t('Active primary shards'),
162 | 'active_shards' => t('Active shards'),
163 | 'relocating_shards' => t('Relocating shards'),
164 | 'initializing_shards' => t('Initializing shards'),
165 | 'unassigned_shards' => t('Unassigned shards'),
166 | 'delayed_unassigned_shards' => t('Delayed unassigned shards'),
167 | 'number_of_pending_tasks' => t('Number of pending tasks'),
168 | 'number_of_in_flight_fetch' => t('Number of in-flight fetch'),
169 | 'task_max_waiting_in_queue_millis' => t(
170 | 'Task max waiting in queue millis'
171 | ),
172 | 'active_shards_percent_as_number' => t(
173 | 'Active shards percent as number'
174 | ),
175 | ];
176 |
177 | foreach ($cluster_status['health'] as $health_key => $health_value) {
178 | $row = [];
179 | $row[] = ['data' => $cluster_health_mapping[$health_key]];
180 | $row[] = ['data' => ($health_value === FALSE ? 'False' : $health_value)];
181 | $cluster_health_rows[] = $row;
182 | }
183 | }
184 |
185 | $output['cluster_statistics_wrapper'] = [
186 | '#type' => 'fieldset',
187 | '#title' => t('Cluster statistics'),
188 | '#collapsible' => TRUE,
189 | '#collapsed' => FALSE,
190 | '#attributes' => [],
191 | ];
192 |
193 | $output['cluster_statistics_wrapper']['nodes'] = [
194 | '#theme' => 'table',
195 | '#header' => [
196 | ['data' => t('Node name')],
197 | ['data' => t('Documents')],
198 | ['data' => t('Size')],
199 | ],
200 | '#rows' => $node_rows,
201 | '#attributes' => [],
202 | ];
203 |
204 | $output['cluster_statistics_wrapper']['cluster_statistics'] = [
205 | '#theme' => 'table',
206 | '#header' => [
207 | ['data' => t('Total'), 'colspan' => 6],
208 | ],
209 | '#rows' => $cluster_statistics_rows,
210 | '#attributes' => ['class' => ['admin-elasticsearch-statistics']],
211 | ];
212 |
213 | $output['cluster_health'] = [
214 | '#theme' => 'table',
215 | '#header' => [
216 | ['data' => t('Cluster Health'), 'colspan' => 2],
217 | ],
218 | '#rows' => $cluster_health_rows,
219 | '#attributes' => ['class' => ['admin-elasticsearch-health']],
220 | ];
221 |
222 | return $output;
223 | }
224 |
225 | }
226 |
--------------------------------------------------------------------------------
/src/ElasticSearch/ClientManager.php:
--------------------------------------------------------------------------------
1 | moduleHandler = $module_handler;
40 | $this->clientManagerFactory = $clientManagerFactory;
41 | }
42 |
43 | /**
44 | * {@inheritdoc}
45 | */
46 | public function getClientForCluster(Cluster $cluster) {
47 | $hosts = [
48 | [
49 | 'url' => $cluster->url,
50 | 'options' => $cluster->options,
51 | ],
52 | ];
53 |
54 | $hash = json_encode($hosts);
55 | if (!isset($this->clients[$hash])) {
56 | $options = [
57 | 'hosts' => [
58 | $cluster->getRawUrl(),
59 | ],
60 | 'options' => [],
61 | 'curl' => [
62 | CURLOPT_CONNECTTIMEOUT => (!empty($cluster->options['timeout']) ? $cluster->options['timeout'] : Cluster::ELASTICSEARCH_CONNECTOR_DEFAULT_TIMEOUT),
63 | ],
64 | ];
65 |
66 | if ($cluster->options['use_authentication']) {
67 | $options['auth'] = [
68 | $cluster->url => [
69 | 'username' => $cluster->options['username'],
70 | 'password' => $cluster->options['password'],
71 | 'method' => $cluster->options['authentication_type'],
72 | ],
73 | ];
74 | }
75 |
76 | $this->moduleHandler->alter(
77 | 'elasticsearch_connector_load_library_options',
78 | $options,
79 | $cluster
80 | );
81 |
82 | $this->clients[$hash] = $this->clientManagerFactory->create($options);
83 | }
84 |
85 | return $this->clients[$hash];
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/ElasticSearch/ClientManagerInterface.php:
--------------------------------------------------------------------------------
1 | getValue())) {
24 | switch ($condition->getOperator()) {
25 | case '<>':
26 | $filter = [
27 | 'exists' => ['field' => $condition->getField()],
28 | ];
29 | break;
30 |
31 | case '=':
32 | $filter = [
33 | 'bool' => [
34 | 'must_not' => [
35 | 'exists' => ['field' => $condition->getField()],
36 | ],
37 | ],
38 | ];
39 | break;
40 |
41 | default:
42 | throw new \Exception(
43 | 'Value is empty for ' . $condition->getField() . '. Incorrect filter criteria is using for searching!'
44 | );
45 | }
46 | }
47 | // Normal filters.
48 | else {
49 | switch ($condition->getOperator()) {
50 | case '=':
51 | $filter = [
52 | 'term' => [$condition->getField() => $condition->getValue()],
53 | ];
54 | break;
55 |
56 | case 'IN':
57 | $filter = [
58 | 'terms' => [$condition->getField() => array_values($condition->getValue())],
59 | ];
60 | break;
61 |
62 | case 'NOT IN':
63 | $filter = [
64 | 'bool' => [
65 | 'must_not' => [
66 | 'terms' => [$condition->getField() => array_values($condition->getValue())],
67 | ],
68 | ]
69 | ];
70 | break;
71 |
72 | case '<>':
73 | $filter = [
74 | 'bool' => [
75 | 'must_not' => [
76 | 'term' => [$condition->getField() => $condition->getValue()],
77 | ],
78 | ]
79 | ];
80 | break;
81 |
82 | case '>':
83 | $filter = [
84 | 'range' => [
85 | $condition->getField() => [
86 | 'from' => $condition->getValue(),
87 | 'to' => NULL,
88 | 'include_lower' => FALSE,
89 | 'include_upper' => FALSE,
90 | ],
91 | ],
92 | ];
93 | break;
94 |
95 | case '>=':
96 | $filter = [
97 | 'range' => [
98 | $condition->getField() => [
99 | 'from' => $condition->getValue(),
100 | 'to' => NULL,
101 | 'include_lower' => TRUE,
102 | 'include_upper' => FALSE,
103 | ],
104 | ],
105 | ];
106 | break;
107 |
108 | case '<':
109 | $filter = [
110 | 'range' => [
111 | $condition->getField() => [
112 | 'from' => NULL,
113 | 'to' => $condition->getValue(),
114 | 'include_lower' => FALSE,
115 | 'include_upper' => FALSE,
116 | ],
117 | ],
118 | ];
119 | break;
120 |
121 | case '<=':
122 | $filter = [
123 | 'range' => [
124 | $condition->getField() => [
125 | 'from' => NULL,
126 | 'to' => $condition->getValue(),
127 | 'include_lower' => FALSE,
128 | 'include_upper' => TRUE,
129 | ],
130 | ],
131 | ];
132 | break;
133 |
134 | case 'BETWEEN':
135 | $filter = [
136 | 'range' => [
137 | $condition->getField() => [
138 | 'from' => (!empty($condition->getValue()[0])) ? $condition->getValue()[0] : NULL,
139 | 'to' => (!empty($condition->getValue()[1])) ? $condition->getValue()[1] : NULL,
140 | 'include_lower' => FALSE,
141 | 'include_upper' => FALSE,
142 | ],
143 | ],
144 | ];
145 | break;
146 |
147 | case 'NOT BETWEEN':
148 | $filter = [
149 | 'bool' => [
150 | 'must_not' => [
151 | 'range' => [
152 | $condition->getField() => [
153 | 'from' => (!empty($condition->getValue()[0])) ? $condition->getValue()[0] : NULL,
154 | 'to' => (!empty($condition->getValue()[1])) ? $condition->getValue()[1] : NULL,
155 | 'include_lower' => FALSE,
156 | 'include_upper' => FALSE,
157 | ],
158 | ],
159 | ]
160 | ]
161 | ];
162 | break;
163 |
164 | default:
165 | throw new \Exception('Undefined operator ' . $condition->getOperator() . ' for ' . $condition->getField() . ' field! Incorrect filter criteria is using for searching!');
166 | }
167 | }
168 |
169 | return $filter;
170 | }
171 |
172 | }
173 |
--------------------------------------------------------------------------------
/src/ElasticSearch/Parameters/Factory/IndexFactory.php:
--------------------------------------------------------------------------------
1 | id();
45 | }
46 |
47 | return $params;
48 | }
49 |
50 | /**
51 | * Build parameters required to create an index
52 | * TODO: Add the timeout option.
53 | *
54 | * @param \Drupal\search_api\IndexInterface $index
55 | *
56 | * @return array
57 | */
58 | public static function create(IndexInterface $index) {
59 | $indexName = static::getIndexName($index);
60 | $indexConfig = [
61 | 'index' => $indexName,
62 | 'body' => [
63 | 'settings' => [
64 | 'number_of_shards' => $index->getOption('number_of_shards', 5),
65 | 'number_of_replicas' => $index->getOption('number_of_replicas', 1),
66 | ],
67 | ],
68 | ];
69 |
70 | // Allow other modules to alter index config before we create it.
71 | $dispatcher = \Drupal::service('event_dispatcher');
72 | $prepareIndexEvent = new PrepareIndexEvent($indexConfig, $indexName);
73 | $event = $dispatcher->dispatch(PrepareIndexEvent::PREPARE_INDEX, $prepareIndexEvent);
74 | $indexConfig = $event->getIndexConfig();
75 |
76 | return $indexConfig;
77 | }
78 |
79 | /**
80 | * Build parameters to bulk delete indexes.
81 | *
82 | * @param \Drupal\search_api\IndexInterface $index
83 | * @param array $ids
84 | *
85 | * @return array
86 | */
87 | public static function bulkDelete(IndexInterface $index, array $ids) {
88 | $params = IndexFactory::index($index, TRUE);
89 | foreach ($ids as $id) {
90 | $params['body'][] = [
91 | 'delete' => [
92 | '_index' => $params['index'],
93 | '_type' => $params['type'],
94 | '_id' => $id,
95 | ],
96 | ];
97 | }
98 |
99 | return $params;
100 | }
101 |
102 | /**
103 | * Build parameters to bulk delete indexes.
104 | *
105 | * @param \Drupal\search_api\IndexInterface $index
106 | * Index object.
107 | * @param \Drupal\search_api\Item\ItemInterface[] $items
108 | * An array of items to be indexed, keyed by their item IDs.
109 | *
110 | * @return array
111 | * Array of parameters to send along to Elasticsearch to perform the bulk
112 | * index.
113 | */
114 | public static function bulkIndex(IndexInterface $index, array $items) {
115 | $params = static::index($index, TRUE);
116 |
117 | foreach ($items as $id => $item) {
118 | $data = [
119 | '_language' => $item->getLanguage(),
120 | ];
121 | /** @var \Drupal\search_api\Item\FieldInterface $field */
122 | foreach ($item as $name => $field) {
123 | $field_type = $field->getType();
124 | if (!empty($field->getValues())) {
125 | $values = array();
126 | foreach ($field->getValues() as $value) {
127 | switch ($field_type) {
128 | case 'string':
129 | $values[] = (string) $value;
130 | break;
131 |
132 | case 'text':
133 | $values[] = $value->toText();
134 | break;
135 |
136 | case 'boolean':
137 | $values[] = (boolean) $value;
138 | break;
139 |
140 | default:
141 | $values[] = $value;
142 | }
143 | }
144 | $data[$field->getFieldIdentifier()] = $values;
145 | }
146 | }
147 | $params['body'][] = ['index' => ['_id' => $id]];
148 | $params['body'][] = $data;
149 | }
150 |
151 | // Allow other modules to alter index params before we send them.
152 | $indexName = IndexFactory::getIndexName($index);
153 | $dispatcher = \Drupal::service('event_dispatcher');
154 | $buildIndexParamsEvent = new BuildIndexParamsEvent($params, $indexName);
155 | $event = $dispatcher->dispatch(BuildIndexParamsEvent::BUILD_PARAMS, $buildIndexParamsEvent);
156 | $params = $event->getElasticIndexParams();
157 |
158 | return $params;
159 | }
160 |
161 | /**
162 | * Build parameters required to create an index mapping.
163 | *
164 | * TODO: We need also:
165 | * $params['index'] - (Required)
166 | * ['type'] - The name of the document type
167 | * ['timeout'] - (time) Explicit operation timeout.
168 | *
169 | * @param \Drupal\search_api\IndexInterface $index
170 | * Index object.
171 | *
172 | * @return array
173 | * Parameters required to create an index mapping.
174 | */
175 | public static function mapping(IndexInterface $index) {
176 | $params = static::index($index, TRUE);
177 |
178 | $properties = [
179 | 'id' => [
180 | 'type' => 'keyword',
181 | 'index' => 'true',
182 | ],
183 | ];
184 |
185 | // Figure out which fields are used for autocompletion if any.
186 | if (\Drupal::moduleHandler()->moduleExists('search_api_autocomplete')) {
187 | $autocompletes = \Drupal::entityTypeManager()->getStorage('search_api_autocomplete_search')->loadMultiple();
188 | $all_autocompletion_fields = [];
189 | foreach ($autocompletes as $autocomplete) {
190 | $suggester = \Drupal::service('plugin.manager.search_api_autocomplete.suggester');
191 | $plugin = $suggester->createInstance('server', ['#search' => $autocomplete]);
192 | assert($plugin instanceof SuggesterInterface);
193 | $configuration = $plugin->getConfiguration();
194 | $autocompletion_fields = isset($configuration['fields']) ? $configuration['fields'] : [];
195 | if (!$autocompletion_fields) {
196 | $autocompletion_fields = $plugin->getSearch()->getIndex()->getFulltextFields();
197 | }
198 |
199 | // Collect autocompletion fields in an array keyed by field id.
200 | $all_autocompletion_fields += array_flip($autocompletion_fields);
201 | }
202 | }
203 |
204 | // Map index fields.
205 | foreach ($index->getFields() as $field_id => $field_data) {
206 | $properties[$field_id] = MappingFactory::mappingFromField($field_data);
207 | // Enable fielddata for fields that are used with autocompletion.
208 | if (isset($all_autocompletion_fields[$field_id])) {
209 | $properties[$field_id]['fielddata'] = TRUE;
210 | }
211 | }
212 |
213 | $properties['_language'] = [
214 | 'type' => 'keyword',
215 | ];
216 |
217 | $params['body'][$params['type']]['properties'] = $properties;
218 |
219 | // Allow other modules to alter index mapping before we create it.
220 | $dispatcher = \Drupal::service('event_dispatcher');
221 | $prepareIndexMappingEvent = new PrepareIndexMappingEvent($params, $params['index']);
222 | $event = $dispatcher->dispatch(PrepareIndexMappingEvent::PREPARE_INDEX_MAPPING, $prepareIndexMappingEvent);
223 | $params = $event->getIndexMappingParams();
224 |
225 | return $params;
226 | }
227 |
228 | /**
229 | * Helper function. Returns the Elasticsearch name of an index.
230 | *
231 | * @param \Drupal\search_api\IndexInterface $index
232 | * Index object.
233 | *
234 | * @return string
235 | * The name of the index on the Elasticsearch server. Includes a prefix for
236 | * uniqueness, the database name, and index machine name.
237 | */
238 | public static function getIndexName(IndexInterface $index) {
239 |
240 | // Get index machine name.
241 | $index_machine_name = is_string($index) ? $index : $index->id();
242 |
243 | // Get prefix and suffix from the cluster if present.
244 | $cluster_id = $index->getServerInstance()->getBackend()->getCluster();
245 | $cluster_options = Cluster::load($cluster_id)->options;
246 |
247 | $index_suffix = '';
248 | if (!empty($cluster_options['rewrite']['rewrite_index'])) {
249 | $index_prefix = isset($cluster_options['rewrite']['index']['prefix']) ? $cluster_options['rewrite']['index']['prefix'] : '';
250 | if ($index_prefix && substr($index_prefix, -1) !== '_') {
251 | $index_prefix .= '_';
252 | }
253 | $index_suffix = isset($cluster_options['rewrite']['index']['suffix']) ? $cluster_options['rewrite']['index']['suffix'] : '';
254 | if ($index_suffix && $index_suffix[0] !== '_') {
255 | $index_suffix = '_' . $index_suffix;
256 | }
257 | }
258 | else {
259 | // If a custom rewrite is not enabled, set prefix to db name by default.
260 | $options = \Drupal::database()->getConnectionOptions();
261 | $index_prefix = 'elasticsearch_index_' . $options['database'] . '_';
262 | }
263 |
264 | return strtolower(preg_replace(
265 | '/[^A-Za-z0-9_]+/',
266 | '',
267 | $index_prefix . $index_machine_name . $index_suffix
268 | ));
269 | }
270 |
271 | }
272 |
--------------------------------------------------------------------------------
/src/ElasticSearch/Parameters/Factory/MappingFactory.php:
--------------------------------------------------------------------------------
1 | getType();
24 | $mappingConfig = NULL;
25 |
26 | switch ($type) {
27 | case 'text':
28 | $mappingConfig = [
29 | 'type' => 'text',
30 | 'boost' => $field->getBoost(),
31 | 'fields' => [
32 | "keyword" => [
33 | "type" => 'keyword',
34 | 'ignore_above' => 256,
35 | ]
36 | ]
37 | ];
38 | break;
39 |
40 | case 'uri':
41 | case 'string':
42 | case 'token':
43 | $mappingConfig = [
44 | 'type' => 'keyword',
45 | ];
46 | break;
47 |
48 | case 'integer':
49 | case 'duration':
50 | $mappingConfig = [
51 | 'type' => 'integer',
52 | ];
53 | break;
54 |
55 | case 'boolean':
56 | $mappingConfig = [
57 | 'type' => 'boolean',
58 | ];
59 | break;
60 |
61 | case 'decimal':
62 | $mappingConfig = [
63 | 'type' => 'float',
64 | ];
65 | break;
66 |
67 | case 'date':
68 | $mappingConfig = [
69 | 'type' => 'date',
70 | 'format' => 'strict_date_optional_time||epoch_second',
71 | ];
72 | break;
73 |
74 | case 'attachment':
75 | $mappingConfig = [
76 | 'type' => 'attachment',
77 | ];
78 | break;
79 |
80 | case 'object':
81 | $mappingConfig = [
82 | 'type' => 'nested',
83 | ];
84 | break;
85 |
86 | case 'location':
87 | $mappingConfig = [
88 | 'type' => 'geo_point',
89 | ];
90 | break;
91 | }
92 |
93 | // Allow other modules to alter mapping config before we create it.
94 | $dispatcher = \Drupal::service('event_dispatcher');
95 | $prepareMappingEvent = new PrepareMappingEvent($mappingConfig, $type, $field);
96 | $event = $dispatcher->dispatch(PrepareMappingEvent::PREPARE_MAPPING, $prepareMappingEvent);
97 | $mappingConfig = $event->getMappingConfig();
98 |
99 | return $mappingConfig;
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/ElasticSearch/Parameters/Factory/SearchFactory.php:
--------------------------------------------------------------------------------
1 | build();
27 | }
28 |
29 | /**
30 | * Parse a Elasticsearch response into a ResultSetInterface.
31 | *
32 | * TODO: Add excerpt handling.
33 | *
34 | * @param \Drupal\search_api\Query\QueryInterface $query
35 | * Search API query.
36 | * @param array $response
37 | * Raw response array back from Elasticsearch.
38 | *
39 | * @return \Drupal\search_api\Query\ResultSetInterface
40 | * The results of the search.
41 | */
42 | public static function parseResult(QueryInterface $query, array $response) {
43 | $index = $query->getIndex();
44 |
45 | // Set up the results array.
46 | $results = $query->getResults();
47 | $results->setExtraData('elasticsearch_response', $response);
48 | $results->setResultCount($response['hits']['total']);
49 | /** @var \Drupal\search_api\Utility\FieldsHelper $fields_helper */
50 | $fields_helper = \Drupal::getContainer()->get('search_api.fields_helper');
51 |
52 | // Add each search result to the results array.
53 | if (!empty($response['hits']['hits'])) {
54 | foreach ($response['hits']['hits'] as $result) {
55 | $result_item = $fields_helper->createItem($index, $result['_id']);
56 | $result_item->setScore($result['_score']);
57 |
58 | // Set each item in _source as a field in Search API.
59 | foreach ($result['_source'] as $elasticsearch_property_id => $elasticsearch_property) {
60 | // Make everything a multifield.
61 | if (!is_array($elasticsearch_property)) {
62 | $elasticsearch_property = [$elasticsearch_property];
63 | }
64 | $field = $fields_helper->createField($index, $elasticsearch_property_id, ['property_path' => $elasticsearch_property_id]);
65 | $field->setValues($elasticsearch_property);
66 | $result_item->setField($elasticsearch_property_id, $field);
67 | }
68 |
69 | $results->addResultItem($result_item);
70 | }
71 | }
72 |
73 | return $results;
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/Entity/Cluster.php:
--------------------------------------------------------------------------------
1 | cluster_id) ? $this->cluster_id : NULL;
99 | }
100 |
101 |
102 | /**
103 | * Get the full base URL of the cluster, including any authentication.
104 | *
105 | * @return string
106 | */
107 | public function getSafeUrl() {
108 | $options = $this->options;
109 | $url_parsed = parse_url($this->url);
110 | if ($options['use_authentication']) {
111 | return $url_parsed['scheme'] . '://'
112 | . $options['username'] . ':****@'
113 | . $url_parsed['host']
114 | . (isset($url_parsed['port']) ? ':' . $url_parsed['port'] : '');
115 | }
116 | else {
117 | return $url_parsed['scheme'] . '://'
118 | . (isset($url_parsed['user']) ? $url_parsed['user'] . ':****@' : '')
119 | . $url_parsed['host']
120 | . (isset($url_parsed['port']) ? ':' . $url_parsed['port'] : '');
121 | }
122 | }
123 |
124 | /**
125 | * Get the raw url.
126 | *
127 | * @return string
128 | */
129 | public function getRawUrl() {
130 | return $this->url;
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/src/Entity/ClusterRouteProvider.php:
--------------------------------------------------------------------------------
1 | addDefaults(
24 | [
25 | '_controller' => '\Drupal\elasticsearch_connector\Controller\ElasticsearchController::getInfo',
26 | '_title_callback' => '\Drupal\elasticsearch_connector\Controller\ElasticsearchController::pageTitle',
27 | '_title' => 'Cluster Info',
28 | ]
29 | )
30 | ->setRequirement('_entity_access', 'elasticsearch_cluster.view')
31 | ->setOptions(
32 | [
33 | 'parameters' => [
34 | 'elasticsearch_cluster' => [
35 | 'with_config_overrides' => TRUE,
36 | ],
37 | ],
38 | ]
39 | );
40 | $route_collection->add('entity.elasticsearch_cluster.canonical', $route);
41 |
42 | $route = (new Route('/admin/config/search/elasticsearch-connector/cluster/{elasticsearch_cluster}/delete'))
43 | ->addDefaults(
44 | [
45 | '_entity_form' => 'elasticsearch_cluster.delete',
46 | '_title' => 'Delete cluster',
47 | ]
48 | )
49 | ->setRequirement('_entity_access', 'elasticsearch_cluster.delete');
50 | $route_collection->add('entity.elasticsearch_cluster.delete_form', $route);
51 |
52 | $route = (new Route('/admin/config/search/elasticsearch-connector/cluster/{elasticsearch_cluster}/edit'))
53 | ->addDefaults(
54 | [
55 | '_entity_form' => 'elasticsearch_cluster.default',
56 | '_title_callback' => '\Drupal\elasticsearch_connector\Controller\ElasticsearchController::pageTitle',
57 | '_title' => 'Edit cluster',
58 | ]
59 | )
60 | ->setRequirement('_entity_access', 'elasticsearch_cluster.update');
61 | $route_collection->add('entity.elasticsearch_cluster.edit_form', $route);
62 |
63 | $route = (new Route('/admin/config/search/elasticsearch-connector/cluster/add'))
64 | ->addDefaults(
65 | [
66 | '_entity_form' => 'elasticsearch_cluster.default',
67 | '_title' => 'Add cluster',
68 | ]
69 | )
70 | ->setRequirement('_entity_create_access', 'elasticsearch_cluster');
71 | $route_collection->add('entity.elasticsearch_cluster.add_form', $route);
72 |
73 | return $route_collection;
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/Entity/Index.php:
--------------------------------------------------------------------------------
1 | index_id) ? $this->index_id : NULL;
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/Entity/IndexRouteProvider.php:
--------------------------------------------------------------------------------
1 | addDefaults(
24 | [
25 | '_entity_form' => 'elasticsearch_index.delete',
26 | '_title' => 'Delete index',
27 | ]
28 | )
29 | ->setRequirement('_entity_access', 'elasticsearch_index.delete');
30 | $route_collection->add('entity.elasticsearch_index.delete_form', $route);
31 |
32 | $route = (new Route('/admin/config/search/elasticsearch-connector/index/add'))
33 | ->addDefaults(
34 | [
35 | '_entity_form' => 'elasticsearch_index.default',
36 | '_title' => 'Add index',
37 | ]
38 | )
39 | ->setRequirement('_entity_create_access', 'elasticsearch_index');
40 | $route_collection->add('entity.elasticsearch_index.add_form', $route);
41 |
42 | return $route_collection;
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/Event/BuildIndexParamsEvent.php:
--------------------------------------------------------------------------------
1 | params = $params;
27 | $this->indexName = $indexName;
28 | }
29 |
30 | /**
31 | * Getter for the params config array.
32 | *
33 | * @return params
34 | */
35 | public function getElasticIndexParams() {
36 | return $this->params;
37 | }
38 |
39 | /**
40 | * Setter for the params config array.
41 | *
42 | * @param $params
43 | */
44 | public function setElasticIndexParams($params) {
45 | $this->params = $params;
46 | }
47 |
48 | /**
49 | * Getter for the index name.
50 | *
51 | * @return indexName
52 | */
53 | public function getIndexName() {
54 | return $this->indexName;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Event/BuildSearchParamsEvent.php:
--------------------------------------------------------------------------------
1 | params = $params;
27 | $this->indexName = $indexName;
28 | }
29 |
30 | /**
31 | * Getter for the params config array.
32 | *
33 | * @return params
34 | */
35 | public function getElasticSearchParams() {
36 | return $this->params;
37 | }
38 |
39 | /**
40 | * Setter for the params config array.
41 | *
42 | * @param $params
43 | */
44 | public function setElasticSearchParams($params) {
45 | $this->params = $params;
46 | }
47 |
48 | /**
49 | * Getter for the index name.
50 | *
51 | * @return indexName
52 | */
53 | public function getIndexName() {
54 | return $this->indexName;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Event/PrepareIndexEvent.php:
--------------------------------------------------------------------------------
1 | indexConfig = $indexConfig;
27 | $this->indexName = $indexName;
28 | }
29 |
30 | /**
31 | * Getter for the index config array.
32 | *
33 | * @return indexConfig
34 | */
35 | public function getIndexConfig() {
36 | return $this->indexConfig;
37 | }
38 |
39 | /**
40 | * Setter for the index config array.
41 | *
42 | * @param $indexConfig
43 | */
44 | public function setIndexConfig($indexConfig) {
45 | $this->indexConfig = $indexConfig;
46 | }
47 |
48 | /**
49 | * Getter for the index name.
50 | *
51 | * @return indexName
52 | */
53 | public function getIndexName() {
54 | return $this->indexName;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Event/PrepareIndexMappingEvent.php:
--------------------------------------------------------------------------------
1 | indexMappingParams = $indexMappingParams;
27 | $this->indexName = $indexName;
28 | }
29 |
30 | /**
31 | * Getter for the index params array.
32 | *
33 | * @return indexMappingParams
34 | */
35 | public function getIndexMappingParams() {
36 | return $this->indexMappingParams;
37 | }
38 |
39 | /**
40 | * Setter for the index params array.
41 | *
42 | * @param $indexMappingParams
43 | */
44 | public function setIndexMappingParams($indexMappingParams) {
45 | $this->indexMappingParams = $indexMappingParams;
46 | }
47 |
48 | /**
49 | * Getter for the index name.
50 | *
51 | * @return indexName
52 | */
53 | public function getIndexName() {
54 | return $this->indexName;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Event/PrepareMappingEvent.php:
--------------------------------------------------------------------------------
1 | mappingConfig = $mappingConfig;
29 | $this->type = $type;
30 | $this->field = $field;
31 | }
32 |
33 | /**
34 | * Getter for the mapping config array.
35 | *
36 | * @return mappingConfig
37 | */
38 | public function getMappingConfig() {
39 | return $this->mappingConfig;
40 | }
41 |
42 | /**
43 | * Setter for the mapping config array.
44 | *
45 | * @param $mappingConfig
46 | */
47 | public function setMappingConfig($mappingConfig) {
48 | $this->mappingConfig = $mappingConfig;
49 | }
50 |
51 | /**
52 | * Getter for the mapping type.
53 | *
54 | * @return type
55 | */
56 | public function getMappingType() {
57 | return $this->type;
58 | }
59 |
60 | /**
61 | * Getter for the field.
62 | *
63 | * @return field
64 | */
65 | public function getMappingField() {
66 | return $this->field;
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/Event/PrepareSearchQueryEvent.php:
--------------------------------------------------------------------------------
1 | elasticSearchQuery = $elasticSearchQuery;
27 | $this->indexName = $indexName;
28 | }
29 |
30 | /**
31 | * Getter for the elasticSearchQuery config array.
32 | *
33 | * @return elasticSearchQuery
34 | */
35 | public function getElasticSearchQuery() {
36 | return $this->elasticSearchQuery;
37 | }
38 |
39 | /**
40 | * Setter for the elasticSearchQuery config array.
41 | *
42 | * @param $elasticSearchQuery
43 | */
44 | public function setElasticSearchQuery($elasticSearchQuery) {
45 | $this->elasticSearchQuery = $elasticSearchQuery;
46 | }
47 |
48 | /**
49 | * Getter for the index name.
50 | *
51 | * @return indexName
52 | */
53 | public function getIndexName() {
54 | return $this->indexName;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Exception/ElasticSearchConnectorException.php:
--------------------------------------------------------------------------------
1 | entityManager = $entity_manager;
57 | $this->clientManager = $client_manager;
58 | $this->clusterManager = $cluster_manager;
59 | }
60 |
61 | /**
62 | *
63 | */
64 | static public function create(ContainerInterface $container) {
65 | return new static (
66 | $container->get('entity_type.manager'),
67 | $container->get('elasticsearch_connector.client_manager'),
68 | $container->get('elasticsearch_connector.cluster_manager')
69 | );
70 | }
71 |
72 | /**
73 | * {@inheritdoc}
74 | */
75 | public function getQuestion() {
76 | return t(
77 | 'Are you sure you want to delete the cluster %title?',
78 | ['%title' => $this->entity->label()]
79 | );
80 | }
81 |
82 | /**
83 | * {@inheritdoc}
84 | */
85 | public function getDescription() {
86 | return $this->t(
87 | 'Deleting a cluster will disable all its indexes and their searches.'
88 | );
89 | }
90 |
91 | /**
92 | * {@inheritdoc}
93 | */
94 | public function submitForm(array &$form, FormStateInterface $form_state) {
95 | $storage = $this->entityManager->getStorage('elasticsearch_index');
96 | $indices = $storage->loadByProperties(
97 | ['server' => $this->entity->cluster_id]
98 | );
99 |
100 | // TODO: handle indices linked to the cluster being deleted.
101 | if (count($indices)) {
102 | $this->messenger()->addError(
103 | $this->t(
104 | 'The cluster %title cannot be deleted as it still has indices.',
105 | ['%title' => $this->entity->label()]
106 | )
107 | );
108 | return;
109 | }
110 |
111 | if ($this->entity->id() == $this->clusterManager->getDefaultCluster()) {
112 | $this->messenger()->addError(
113 | $this->t(
114 | 'The cluster %title cannot be deleted as it is set as the default cluster.',
115 | ['%title' => $this->entity->label()]
116 | )
117 | );
118 | }
119 | else {
120 | $this->entity->delete();
121 | $this->messenger()->addMessage(
122 | $this->t(
123 | 'The cluster %title has been deleted.',
124 | ['%title' => $this->entity->label()]
125 | )
126 | );
127 | $form_state->setRedirect('elasticsearch_connector.config_entity.list');
128 | }
129 | }
130 |
131 | /**
132 | * {@inheritdoc}
133 | */
134 | public function getConfirmText() {
135 | return $this->t('Delete');
136 | }
137 |
138 | /**
139 | * {@inheritdoc}
140 | */
141 | public function getCancelUrl() {
142 | return new Url('elasticsearch_connector.config_entity.list');
143 | }
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/src/Form/ClusterForm.php:
--------------------------------------------------------------------------------
1 | clientManager = $client_manager;
40 | $this->clusterManager = $cluster_manager;
41 | }
42 |
43 | /**
44 | *
45 | */
46 | static public function create(ContainerInterface $container) {
47 | return new static (
48 | $container->get('elasticsearch_connector.client_manager'),
49 | $container->get('elasticsearch_connector.cluster_manager')
50 | );
51 | }
52 |
53 | /**
54 | * {@inheritdoc}
55 | */
56 | public function form(array $form, FormStateInterface $form_state) {
57 | if ($form_state->isRebuilding()) {
58 | $this->entity = $this->buildEntity($form, $form_state);
59 | }
60 | $form = parent::form($form, $form_state);
61 | if ($this->entity->isNew()) {
62 | $form['#title'] = $this->t('Add Elasticsearch Cluster');
63 | }
64 | else {
65 | $form['#title'] = $this->t(
66 | 'Edit Elasticsearch Cluster @label',
67 | array('@label' => $this->entity->label())
68 | );
69 | }
70 |
71 | $this->buildEntityForm($form, $form_state);
72 |
73 | return $form;
74 | }
75 |
76 | /**
77 | * {@inheritdoc}
78 | */
79 | public function buildEntityForm(array &$form, FormStateInterface $form_state) {
80 | $form['cluster'] = array(
81 | '#type' => 'value',
82 | '#value' => $this->entity,
83 | );
84 |
85 | $form['name'] = array(
86 | '#type' => 'textfield',
87 | '#title' => t('Administrative cluster name'),
88 | '#default_value' => empty($this->entity->name) ? '' : $this->entity->name,
89 | '#description' => t(
90 | 'Enter the administrative cluster name that will be your Elasticsearch cluster unique identifier.'
91 | ),
92 | '#required' => TRUE,
93 | '#weight' => 1,
94 | );
95 |
96 | $form['cluster_id'] = array(
97 | '#type' => 'machine_name',
98 | '#title' => t('Cluster id'),
99 | '#default_value' => !empty($this->entity->cluster_id) ? $this->entity->cluster_id : '',
100 | '#maxlength' => 125,
101 | '#description' => t(
102 | 'A unique machine-readable name for this Elasticsearch cluster.'
103 | ),
104 | '#machine_name' => array(
105 | 'exists' => ['Drupal\elasticsearch_connector\Entity\Cluster', 'load'],
106 | 'source' => array('name'),
107 | ),
108 | '#required' => TRUE,
109 | '#disabled' => !empty($this->entity->cluster_id),
110 | '#weight' => 2,
111 | );
112 |
113 | $form['url'] = array(
114 | '#type' => 'textfield',
115 | '#title' => t('Server URL'),
116 | '#default_value' => !empty($this->entity->url) ? $this->entity->url : '',
117 | '#description' => t(
118 | 'URL and port of a server (node) in the cluster. ' .
119 | 'Please, always enter the port even if it is default one. ' .
120 | 'Nodes will be automatically discovered. ' .
121 | 'Examples: http://localhost:9200 or https://localhost:443.'
122 | ),
123 | '#required' => TRUE,
124 | '#weight' => 3,
125 | );
126 |
127 | $form['status_info'] = $this->clusterFormInfo();
128 |
129 | $default = $this->clusterManager->getDefaultCluster();
130 | $form['default'] = array(
131 | '#type' => 'checkbox',
132 | '#title' => t('Make this cluster default connection'),
133 | '#description' => t(
134 | 'If the cluster connection is not specified the API will use the default connection.'
135 | ),
136 | '#default_value' => (empty($default) || (!empty($this->entity->cluster_id) && $this->entity->cluster_id == $default)) ? '1' : '0',
137 | '#weight' => 4,
138 | );
139 |
140 | $form['options'] = array(
141 | '#tree' => TRUE,
142 | '#weight' => 5,
143 | );
144 |
145 | $form['options']['multiple_nodes_connection'] = array(
146 | '#type' => 'checkbox',
147 | '#title' => t('Use multiple nodes connection'),
148 | '#description' => t(
149 | 'Automatically discover all nodes and use them in the cluster connection. ' .
150 | 'Then the Elasticsearch client can distribute the query execution on random base between nodes.'
151 | ),
152 | '#default_value' => (!empty($this->entity->options['multiple_nodes_connection']) ? 1 : 0),
153 | '#weight' => 5.1,
154 | );
155 |
156 | $form['status'] = array(
157 | '#type' => 'radios',
158 | '#title' => t('Status'),
159 | '#default_value' => isset($this->entity->status) ? $this->entity->status : Cluster::ELASTICSEARCH_CONNECTOR_STATUS_ACTIVE,
160 | '#options' => array(
161 | Cluster::ELASTICSEARCH_CONNECTOR_STATUS_ACTIVE => t('Active'),
162 | Cluster::ELASTICSEARCH_CONNECTOR_STATUS_INACTIVE => t('Inactive'),
163 | ),
164 | '#required' => TRUE,
165 | '#weight' => 6,
166 | );
167 |
168 | $form['options']['use_authentication'] = array(
169 | '#type' => 'checkbox',
170 | '#title' => t('Use authentication'),
171 | '#description' => t(
172 | 'Use HTTP authentication method to connect to Elasticsearch.'
173 | ),
174 | '#default_value' => (!empty($this->entity->options['use_authentication']) ? 1 : 0),
175 | '#suffix' => '
',
176 | '#weight' => 5.2,
177 | );
178 |
179 | $form['options']['authentication_type'] = array(
180 | '#type' => 'radios',
181 | '#title' => t('Authentication type'),
182 | '#description' => t('Select the http authentication type.'),
183 | '#options' => array(
184 | 'Basic' => t('Basic'),
185 | 'Digest' => t('Digest'),
186 | 'NTLM' => t('NTLM'),
187 | ),
188 | '#default_value' => (!empty($this->entity->options['authentication_type']) ? $this->entity->options['authentication_type'] : 'Basic'),
189 | '#states' => array(
190 | 'visible' => array(
191 | ':input[name="options[use_authentication]"]' => array('checked' => TRUE),
192 | ),
193 | ),
194 | '#weight' => 5.3,
195 | );
196 |
197 | $form['options']['username'] = array(
198 | '#type' => 'textfield',
199 | '#title' => t('Username'),
200 | '#description' => t('The username for authentication.'),
201 | '#default_value' => (!empty($this->entity->options['username']) ? $this->entity->options['username'] : ''),
202 | '#states' => array(
203 | 'visible' => array(
204 | ':input[name="options[use_authentication]"]' => array('checked' => TRUE),
205 | ),
206 | ),
207 | '#weight' => 5.4,
208 | );
209 |
210 | $form['options']['password'] = array(
211 | '#type' => 'textfield',
212 | '#title' => t('Password'),
213 | '#description' => t('The password for authentication.'),
214 | '#default_value' => (!empty($this->entity->options['password']) ? $this->entity->options['password'] : ''),
215 | '#states' => array(
216 | 'visible' => array(
217 | ':input[name="options[use_authentication]"]' => array('checked' => TRUE),
218 | ),
219 | ),
220 | '#weight' => 5.5,
221 | );
222 |
223 | $form['options']['timeout'] = array(
224 | '#type' => 'number',
225 | '#title' => t('Connection timeout'),
226 | '#size' => 20,
227 | '#required' => TRUE,
228 | '#description' => t(
229 | 'After how many seconds the connection should timeout if there is no connection to Elasticsearch.'
230 | ),
231 | '#default_value' => (!empty($this->entity->options['timeout']) ? $this->entity->options['timeout'] : Cluster::ELASTICSEARCH_CONNECTOR_DEFAULT_TIMEOUT),
232 | '#weight' => 5.6,
233 | );
234 |
235 | $form['options']['rewrite'] = [
236 | '#tree' => TRUE,
237 | '#weight' => 6,
238 | ];
239 |
240 | $form['options']['rewrite']['rewrite_index'] = [
241 | '#title' => $this->t('Alter the Elasticsearch index name.'),
242 | '#type' => 'checkbox',
243 | '#default_value' => (!empty($this->entity->options['rewrite']['rewrite_index']) ? 1 : 0),
244 | '#description' => $this->t('Alter the name of the Elasticsearch index by optionally adding a prefix and suffix to the Search API index name.')
245 | ];
246 |
247 | $form['options']['rewrite']['index']['prefix'] = [
248 | '#type' => 'textfield',
249 | '#title' => $this->t('Index name prefix'),
250 | '#default_value' => (!empty($this->entity->options['rewrite']['index']['prefix']) ? $this->entity->options['rewrite']['index']['prefix'] : ''),
251 | '#description' => $this->t(
252 | 'If a value is provided it will be prepended to the index name.'
253 | ),
254 | '#states' => [
255 | 'visible' => [
256 | ':input[name="options[rewrite][rewrite_index]"]' => ['checked' => TRUE],
257 | ],
258 | ],
259 | '#weight' => 5,
260 | ];
261 |
262 | $form['options']['rewrite']['index']['suffix'] = [
263 | '#type' => 'textfield',
264 | '#title' => $this->t('Index name suffix'),
265 | '#default_value' => (!empty($this->entity->options['rewrite']['index']['suffix']) ? $this->entity->options['rewrite']['index']['suffix'] : ''),
266 | '#description' => $this->t(
267 | 'If a value is provided it will be appended to the index name.'
268 | ),
269 | '#states' => [
270 | 'visible' => [
271 | ':input[name="options[rewrite][rewrite_index]"]' => ['checked' => TRUE],
272 | ],
273 | ],
274 | '#weight' => 10,
275 | ];
276 | }
277 |
278 | /**
279 | * {@inheritdoc}
280 | */
281 | public function validateForm(array &$form, FormStateInterface $form_state) {
282 | parent::validateForm($form, $form_state);
283 | $values = $form_state->getValues();
284 |
285 | // TODO: Check for valid URL when we are submitting the form.
286 | // Set default cluster.
287 | $default = $this->clusterManager->getDefaultCluster();
288 | if (empty($default) && !$values['default']) {
289 | $default = $this->clusterManager->setDefaultCluster($values['cluster_id']);
290 | }
291 | elseif ($values['default']) {
292 | $default = $this->clusterManager->setDefaultCluster($values['cluster_id']);
293 | }
294 |
295 | if ($values['default'] == 0 && !empty($default) && $default == $values['cluster_id']) {
296 | $this->messenger()->addWarning(
297 | t(
298 | 'There must be a default connection. %name is still the default
299 | connection. Please change the default setting on the cluster you wish
300 | to set as default.',
301 | array(
302 | '%name' => $values['name'],
303 | )
304 | )
305 | );
306 | }
307 | }
308 |
309 | /**
310 | * Build the cluster info table for the edit page.
311 | *
312 | * @return array
313 | */
314 | protected function clusterFormInfo() {
315 | $element = array();
316 |
317 | if (isset($this->entity->url)) {
318 | try {
319 | $client_connector = $this->clientManager->getClientForCluster($this->entity);
320 |
321 | $cluster_info = $client_connector->getClusterInfo();
322 | if ($cluster_info) {
323 | $headers = array(
324 | array('data' => t('Cluster name')),
325 | array('data' => t('Status')),
326 | array('data' => t('Number of nodes')),
327 | );
328 |
329 | if (isset($cluster_info['state'])) {
330 | $rows = array(
331 | array(
332 | $cluster_info['health']['cluster_name'],
333 | $cluster_info['health']['status'],
334 | $cluster_info['health']['number_of_nodes'],
335 | ),
336 | );
337 |
338 | $element = array(
339 | '#theme' => 'table',
340 | '#header' => $headers,
341 | '#rows' => $rows,
342 | '#attributes' => array(
343 | 'class' => array('admin-elasticsearch'),
344 | 'id' => 'cluster-info',
345 | ),
346 | );
347 | }
348 | else {
349 | $rows = array(
350 | array(
351 | t('Unknown'),
352 | t('Unavailable'),
353 | '',
354 | ),
355 | );
356 |
357 | $element = array(
358 | '#theme' => 'table',
359 | '#header' => $headers,
360 | '#rows' => $rows,
361 | '#attributes' => array(
362 | 'class' => array('admin-elasticsearch'),
363 | 'id' => 'cluster-info',
364 | ),
365 | );
366 | }
367 | }
368 | else {
369 | $element['#type'] = 'markup';
370 | $element['#markup'] = '
';
371 | }
372 | }
373 | catch (\Exception $e) {
374 | $this->messenger()->addError($e->getMessage());
375 | }
376 | }
377 |
378 | return $element;
379 | }
380 |
381 | /**
382 | * {@inheritdoc}
383 | */
384 | public function save(array $form, FormStateInterface $form_state) {
385 | // Only save the server if the form doesn't need to be rebuilt.
386 | if (!$form_state->isRebuilding()) {
387 | try {
388 | parent::save($form, $form_state);
389 | $this->messenger()->addMessage(t('Cluster %label has been updated.', array('%label' => $this->entity->label())));
390 | $form_state->setRedirect('elasticsearch_connector.config_entity.list');
391 | }
392 | catch (EntityStorageException $e) {
393 | $form_state->setRebuild();
394 | watchdog_exception('elasticsearch_connector', $e);
395 | $this->messenger()->addError(
396 | $this->t('The cluster could not be saved.')
397 | );
398 | }
399 | }
400 | }
401 |
402 | }
403 |
--------------------------------------------------------------------------------
/src/Form/IndexDeleteForm.php:
--------------------------------------------------------------------------------
1 | clientManager = $client_manager;
41 | $this->entityTypeManager = $entity_type_manager;
42 | }
43 |
44 | /**
45 | *
46 | */
47 | static public function create(ContainerInterface $container) {
48 | return new static (
49 | $container->get('elasticsearch_connector.client_manager'),
50 | $container->get('entity_type.manager'),
51 | $container->get('elasticsearch_connector.cluster_manager')
52 | );
53 | }
54 |
55 | /**
56 | * {@inheritdoc}
57 | */
58 | public function getQuestion() {
59 | return $this->t('Are you sure you want to delete the index %title?', array('%title' => $this->entity->label()));
60 | }
61 |
62 | /**
63 | * {@inheritdoc}
64 | */
65 | public function submitForm(array &$form, FormStateInterface $form_state) {
66 | /** @var Cluster $cluster */
67 | $cluster = $this->entityTypeManager->getStorage('elasticsearch_cluster')->load($this->entity->server);
68 | $client = $this->clientManager->getClientForCluster($cluster);
69 | try {
70 | if ($client->indices()
71 | ->exists(array('index' => $this->entity->index_id))
72 | ) {
73 | $client->indices()->delete(['index' => $this->entity->index_id]);
74 | }
75 | $this->entity->delete();
76 | $this->messenger()->addMessage($this->t('The index %title has been deleted.', array('%title' => $this->entity->label())));
77 | $form_state->setRedirect('elasticsearch_connector.config_entity.list');
78 | }
79 | catch (Missing404Exception $e) {
80 | // The index was not found, so just remove it anyway.
81 | $this->messenger()->addError($e->getMessage());
82 | }
83 | catch (\Exception $e) {
84 | $this->messenger()->addError($e->getMessage());
85 | }
86 | }
87 |
88 | /**
89 | * {@inheritdoc}
90 | */
91 | public function getConfirmText() {
92 | return $this->t('Delete');
93 | }
94 |
95 | /**
96 | * {@inheritdoc}
97 | */
98 | public function getCancelUrl() {
99 | return new Url('elasticsearch_connector.config_entity.list');
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/Form/IndexForm.php:
--------------------------------------------------------------------------------
1 | entityTypeManager = $entity_manager;
50 | $this->clientManager = $client_manager;
51 | $this->clusterManager = $cluster_manager;
52 | }
53 |
54 | /**
55 | * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
56 | *
57 | * @return static
58 | */
59 | static public function create(ContainerInterface $container) {
60 | return new static (
61 | $container->get('entity_type.manager'),
62 | $container->get('elasticsearch_connector.client_manager'),
63 | $container->get('elasticsearch_connector.cluster_manager')
64 | );
65 | }
66 |
67 | /**
68 | * Get the entity manager.
69 | *
70 | * @return \Drupal\Core\Entity\EntityManager
71 | * An instance of EntityManager.
72 | */
73 | protected function getEntityManager() {
74 | return $this->entityTypeManager;
75 | }
76 |
77 | /**
78 | * Get the cluster storage controller.
79 | *
80 | * @return \Drupal\Core\Entity\EntityStorageInterface
81 | * An instance of EntityStorageInterface.
82 | */
83 | protected function getClusterStorage() {
84 | return $this->getEntityManager()->getStorage('elasticsearch_cluster');
85 | }
86 |
87 | /**
88 | * Get the index storage controller.
89 | *
90 | * @return \Drupal\Core\Entity\EntityStorageInterface
91 | * An instance of EntityStorageInterface.
92 | */
93 | protected function getIndexStorage() {
94 | return $this->getEntityManager()->getStorage('elasticsearch_index');
95 | }
96 |
97 | /**
98 | * Get all clusters.
99 | *
100 | * @return array
101 | * All clusters
102 | */
103 | protected function getAllClusters() {
104 | $options = array();
105 | foreach (
106 | $this->getClusterStorage()
107 | ->loadMultiple() as $cluster_machine_name
108 | ) {
109 | $options[$cluster_machine_name->cluster_id] = $cluster_machine_name;
110 | }
111 | return $options;
112 | }
113 |
114 | /**
115 | * Get cluster field.
116 | *
117 | * @param string $field
118 | * Field name.
119 | *
120 | * @return array
121 | * All clusters' fields.
122 | */
123 | protected function getClusterField($field) {
124 | $clusters = $this->getAllClusters();
125 | $options = array();
126 | foreach ($clusters as $cluster) {
127 | $options[$cluster->$field] = $cluster->$field;
128 | }
129 | return $options;
130 | }
131 |
132 | /**
133 | * Return url of the selected cluster.
134 | *
135 | * @param string $id
136 | * Cluster id.
137 | *
138 | * @return string
139 | * Cluster url.
140 | */
141 | protected function getSelectedClusterUrl($id) {
142 | $result = NULL;
143 | $clusters = $this->getAllClusters();
144 | foreach ($clusters as $cluster) {
145 | if ($cluster->cluster_id == $id) {
146 | $result = $cluster->url;
147 | }
148 | }
149 | return $result;
150 | }
151 |
152 | /**
153 | * {@inheritdoc}
154 | */
155 | public function form(array $form, FormStateInterface $form_state) {
156 | if ($form_state->isRebuilding()) {
157 | $this->entity = $this->buildEntity($form, $form_state);
158 | }
159 |
160 | $form = parent::form($form, $form_state);
161 | $form['#title'] = $this->t('Index');
162 |
163 | $this->buildEntityForm($form, $form_state);
164 | return $form;
165 | }
166 |
167 | /**
168 | * {@inheritdoc}
169 | */
170 | public function buildEntityForm(array &$form, FormStateInterface $form_state) {
171 | // TODO: Provide check and support for other index modules settings.
172 | // TODO: Provide support for the rest of the dynamic settings.
173 | // TODO: Make sure that on edit the static settings cannot be changed.
174 | // @see https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html
175 | $form['index'] = array(
176 | '#type' => 'value',
177 | '#value' => $this->entity,
178 | );
179 |
180 | $form['name'] = array(
181 | '#type' => 'textfield',
182 | '#title' => t('Index name'),
183 | '#required' => TRUE,
184 | '#default_value' => '',
185 | '#description' => t('Enter the index name.'),
186 | '#weight' => 1,
187 | );
188 |
189 | $form['index_id'] = array(
190 | '#type' => 'machine_name',
191 | '#title' => t('Index id'),
192 | '#default_value' => '',
193 | '#maxlength' => 125,
194 | '#description' => t('Unique, machine-readable identifier for this Index'),
195 | '#machine_name' => array(
196 | 'exists' => array($this->getIndexStorage(), 'load'),
197 | 'source' => array('name'),
198 | 'replace_pattern' => '[^a-z0-9_]+',
199 | 'replace' => '_',
200 | ),
201 | '#required' => TRUE,
202 | '#disabled' => !empty($this->entity->index_id),
203 | '#weight' => 2,
204 | );
205 |
206 | // Here server refers to the elasticsearch cluster.
207 | $form['server'] = array(
208 | '#type' => 'radios',
209 | '#title' => $this->t('Server'),
210 | '#default_value' => !empty($this->entity->server) ? $this->entity->server : $this->clusterManager->getDefaultCluster(),
211 | '#description' => $this->t('Select the server this index should reside on. Index can not be enabled without connection to valid server.'),
212 | '#options' => $this->getClusterField('cluster_id'),
213 | '#weight' => 9,
214 | '#required' => TRUE,
215 | );
216 |
217 | $form['num_of_shards'] = array(
218 | '#type' => 'textfield',
219 | '#title' => t('Number of shards'),
220 | '#required' => TRUE,
221 | '#default_value' => 5,
222 | '#description' => t('Enter the number of shards for the index.'),
223 | '#weight' => 3,
224 | );
225 |
226 | $form['num_of_replica'] = array(
227 | '#type' => 'textfield',
228 | '#title' => t('Number of replica'),
229 | '#default_value' => 1,
230 | '#description' => t('Enter the number of shards replicas.'),
231 | '#weight' => 4,
232 | );
233 |
234 | $form['codec'] = array(
235 | '#type' => 'select',
236 | '#title' => t('Codec'),
237 | '#default_value' => (!empty($this->entity->codec) ? $this->entity->codec : 'default'),
238 | '#description' => t('Select compression for stored data. Defaults to: LZ4.'),
239 | '#options' => array(
240 | 'default' => 'LZ4',
241 | 'best_compression' => 'DEFLATE',
242 | ),
243 | '#weight' => 5,
244 | );
245 | }
246 |
247 | /**
248 | * {@inheritdoc}
249 | */
250 | public function validateForm(array &$form, FormStateInterface $form_state) {
251 | parent::validateForm($form, $form_state);
252 |
253 | $values = $form_state->getValues();
254 |
255 | if (!preg_match('/^[a-z][a-z0-9_]*$/i', $values['index_id'])) {
256 | $form_state->setErrorByName('name', t('Enter an index name that begins with a letter and contains only letters, numbers, and underscores.'));
257 | }
258 |
259 | if (!is_numeric($values['num_of_shards']) || $values['num_of_shards'] < 1) {
260 | $form_state->setErrorByName('num_of_shards', t('Invalid number of shards.'));
261 | }
262 |
263 | if (!is_numeric($values['num_of_replica'])) {
264 | $form_state->setErrorByName('num_of_replica', t('Invalid number of replica.'));
265 | }
266 | }
267 |
268 | /**
269 | * {@inheritdoc}
270 | */
271 | public function save(array $form, FormStateInterface $form_state) {
272 | $cluster = $this->entityTypeManager->getStorage('elasticsearch_cluster')->load($this->entity->server);
273 | $client = $this->clientManager->getClientForCluster($cluster);
274 |
275 | $index_params['index'] = $this->entity->index_id;
276 | $index_params['body']['settings']['number_of_shards'] = $form_state->getValue('num_of_shards');
277 | $index_params['body']['settings']['number_of_replicas'] = $form_state->getValue('num_of_replica');
278 | $index_params['body']['settings']['codec'] = $form_state->getValue('codec');
279 |
280 | try {
281 | $response = $client->indices()->create($index_params);
282 | if ($client->CheckResponseAck($response)) {
283 | $this->messenger()->addMessage(
284 | t(
285 | 'The index %index having id %index_id has been successfully created.',
286 | array(
287 | '%index' => $form_state->getValue('name'),
288 | '%index_id' => $form_state->getValue('index_id'),
289 | )
290 | )
291 | );
292 | }
293 | else {
294 | $this->messenger()->addError(
295 | t(
296 | 'Fail to create the index %index having id @index_id',
297 | array(
298 | '%index' => $form_state->getValue('name'),
299 | '@index_id' => $form_state->getValue('index_id'),
300 | )
301 | )
302 | );
303 | }
304 |
305 | parent::save($form, $form_state);
306 |
307 | $this->messenger()->addMessage(t('Index %label has been added.', array('%label' => $this->entity->label())));
308 |
309 | $form_state->setRedirect('elasticsearch_connector.config_entity.list');
310 | }
311 | catch (\Exception $e) {
312 | $this->messenger()->addError($e->getMessage());
313 | }
314 | }
315 |
316 | }
317 |
--------------------------------------------------------------------------------
/src/Plugin/search_api/backend/SearchApiElasticsearchBackendInterface.php:
--------------------------------------------------------------------------------
1 | t(
36 | 'Select the fields to exclude from the source field in search results. See the Elasticsearch documentation on source filtering for more info.',
37 | [
38 | ':url' => 'https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-source-filtering.html',
39 | ]
40 | );
41 |
42 | foreach ($this->index->getFields() as $field) {
43 | $excluded = !empty($this->configuration['fields'][$field->getFieldIdentifier()]);
44 | $form['fields'][$field->getFieldIdentifier()] = array(
45 | '#type' => 'checkbox',
46 | '#title' => $field->getPrefixedLabel(),
47 | '#default_value' => $excluded,
48 | );
49 | }
50 |
51 | return $form;
52 | }
53 |
54 | public function preprocessSearchQuery(QueryInterface $query) {
55 | $excluded_fields = array_filter($this->configuration['fields']);
56 |
57 | $query->setOption(
58 | 'elasticsearch_connector_exclude_source_fields',
59 | array_keys($excluded_fields)
60 | );
61 | }
62 |
63 | public static function supportsIndex(IndexInterface $index) {
64 | $backend = $index->getServerInstance()->getBackend();
65 | return $backend instanceof SearchApiElasticsearchBackend;
66 | }
67 | }
--------------------------------------------------------------------------------
/tests/modules/elasticsearch_test/config/install/search_api.index.elasticsearch_index.yml:
--------------------------------------------------------------------------------
1 | id: elasticsearch_index
2 | name: 'Test index using elasticsearch module'
3 | description: 'An index used for testing.'
4 | read_only: false
5 | options:
6 | cron_limit: -1
7 | index_directly: false
8 | fields:
9 | 'entity:entity_test/id':
10 | type: integer
11 | 'entity:entity_test/name':
12 | type: text
13 | boost: '5.0'
14 | 'entity:entity_test/body':
15 | type: text
16 | 'entity:entity_test/type':
17 | type: string
18 | 'entity:entity_test/keywords':
19 | type: string
20 | search_api_language:
21 | type: string
22 | processors:
23 | language:
24 | status: true
25 | datasources:
26 | - 'entity:entity_test'
27 | datasource_configs: { }
28 | tracker: default
29 | tracker_config: { }
30 | server: elasticsearch_server
31 | status: 1
32 | langcode: en
33 | dependencies:
34 | config:
35 | - field.instance.entity_test.article.body
36 | - field.instance.entity_test.article.keywords
37 | - field.instance.entity_test.item.body
38 | - field.instance.entity_test.item.keywords
39 | - field.storage.entity_test.body
40 | - field.storage.entity_test.keywords
41 | - search_api.server.elasticsearch_server
42 | module:
43 | - entity_test
44 |
--------------------------------------------------------------------------------
/tests/modules/elasticsearch_test/config/install/search_api.server.elasticsearch_server.yml:
--------------------------------------------------------------------------------
1 | id: elasticsearch_server
2 | name: 'Elasticsearch server'
3 | description: 'Testing Elasticsearch backend.'
4 | backend: elasticsearch
5 | backend_config:
6 | scheme: http
7 | host: localhost
8 | port: '9200'
9 | path: ''
10 | http_user: ''
11 | http_pass: ''
12 | excerpt: 0
13 | retrieve_data: 0
14 | highlight_data: 0
15 | http_method: AUTO
16 | status: 1
17 | langcode: en
18 | dependencies:
19 | module:
20 | - elasticsearch_connector
21 |
--------------------------------------------------------------------------------
/tests/modules/elasticsearch_test/elasticsearch_test.info.yml:
--------------------------------------------------------------------------------
1 | type: module
2 | name: 'Elasticseach Test'
3 | description: 'Elasticsearch Search API backend tests'
4 | package: 'Search API'
5 | dependencies:
6 | - search_api_test_db
7 | core: 8.x
8 | hidden: true
9 |
--------------------------------------------------------------------------------
/tests/src/Behat/behat.yml:
--------------------------------------------------------------------------------
1 | default:
2 | suites:
3 | default:
4 | contexts:
5 | - ElasticsearchConnectorFeatureContext
6 | - Drupal\DrupalExtension\Context\DrupalContext
7 | - Drupal\DrupalExtension\Context\MinkContext
8 | - Drupal\DrupalExtension\Context\MessageContext
9 | extensions:
10 | Behat\MinkExtension:
11 | goutte: ~
12 | selenium2: ~
13 | base_url: http://localhost
14 | sessions:
15 | default:
16 | goutte: ~
17 | javascript:
18 | selenium2:
19 | browser: phantomjs
20 | wd_host: http://localhost:8910/wd/hub
21 | Drupal\DrupalExtension:
22 | blackbox: ~
23 | api_driver: 'drupal'
24 | drush:
25 | alias: 'local'
26 | drupal:
27 | drupal_root: '/var/www/html'
28 | region_map:
29 | footer: "#footer"
30 | selectors:
31 | message_selector: '.messages'
32 | error_message_selector: '.messages--error'
33 | success_message_selector: '.messages--status'
34 | Bex\Behat\ScreenshotExtension:
35 | screenshot_taking_mode: all_scenarios
36 | image_drivers:
37 | local:
38 | screenshot_directory: '/var/www/html/artifacts/screenshots'
39 |
--------------------------------------------------------------------------------
/tests/src/Behat/example.behat.local.yml:
--------------------------------------------------------------------------------
1 | # Local Behat configuration file.
2 | #
3 | # See the testing section of the [README.md](README.md) to learn how to use this file.
4 | default:
5 | suites:
6 | default:
7 | contexts:
8 | - ElasticsearchConnectorFeatureContext
9 | - Drupal\DrupalExtension\Context\DrupalContext
10 | - Drupal\DrupalExtension\Context\MinkContext
11 | - Drupal\DrupalExtension\Context\MessageContext
12 | extensions:
13 | Behat\MinkExtension:
14 | goutte: ~
15 | selenium2:
16 | wd_host: http://MY-IP-ADDRESS:4444/wd/hub
17 | base_url: http://localhost:8080
18 | browser_name: 'chrome'
19 | Drupal\DrupalExtension:
20 | blackbox: ~
21 | api_driver: 'drupal'
22 | drush:
23 | alias: 'local'
24 | drupal:
25 | drupal_root: '/var/www/docroot'
26 | region_map:
27 | footer: "#footer"
28 | selectors:
29 | message_selector: '.messages'
30 | error_message_selector: '.messages.messages--error'
31 | success_message_selector: '.messages.messages--status'
32 |
--------------------------------------------------------------------------------
/tests/src/Behat/features/bootstrap/ElasticsearchConnectorFeatureContext.php:
--------------------------------------------------------------------------------
1 | moduleExists('elasticsearch_connector')) {
24 | \Drupal::service('module_installer')->install(['elasticsearch_connector']);
25 | }
26 |
27 | // Also uninstall the inline form errors module for easier testing.
28 | if ($moduleHandler->moduleExists('inline_form_errors')) {
29 | \Drupal::service('module_installer')->uninstall(['inline_form_errors']);
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/tests/src/Behat/features/bootstrap/settings_form.feature:
--------------------------------------------------------------------------------
1 | @javascript @api
2 | Feature: Test the settings form
3 | In order configure Draco DFP
4 | As an authenticated user
5 | I need to be able to set the module's settings via it's admin form.
6 |
7 | Scenario: Fill and submit the administration form
8 | Given I am logged in as a user with the "administrator" role
9 | When I visit "admin/config/search/elasticsearch-connector"
10 | Then I should see the text "No clusters available."
11 |
--------------------------------------------------------------------------------
/tests/src/Unit/ClusterManagerTest.php:
--------------------------------------------------------------------------------
1 | prophesize(StateInterface::class);
33 | $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
34 |
35 | $this->clusterManager = new ClusterManager($state->reveal(), $entity_type_manager->reveal());
36 | }
37 |
38 | /**
39 | * @covers ::__construct
40 | */
41 | public function testConstruct() {
42 | $this->assertInstanceOf(ClusterManager::class, $this->clusterManager);
43 | }
44 |
45 | /**
46 | * @covers ::getDefaultCluster
47 | * @covers ::setDefaultCluster
48 | */
49 | public function testGetSetDefaultCluster() {
50 | $state = $this->prophesize(StateInterface::class);
51 | $state->get('elasticsearch_connector_get_default_connector', '')
52 | ->willReturn('foo');
53 | $state->set('elasticsearch_connector_get_default_connector', 'foo')
54 | ->shouldBeCalled();
55 |
56 | // Check the get method.
57 | $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
58 | $this->clusterManager = new ClusterManager($state->reveal(), $entity_type_manager->reveal());
59 | $this->assertEquals('foo', $this->clusterManager->getDefaultCluster());
60 |
61 | // Check the set method (a prediction was set above).
62 | $this->clusterManager->setDefaultCluster('foo');
63 | }
64 |
65 | /**
66 | * @covers ::loadAllClusters
67 | */
68 | public function testLoadAllClusters() {
69 | $state = $this->prophesize(StateInterface::class);
70 |
71 | $cluster1 = new \stdClass();
72 | $cluster1->cluster_id = 'foo';
73 | $cluster1->status = FALSE;
74 |
75 | $cluster2 = new \stdClass();
76 | $cluster2->cluster_id = 'bar';
77 | $cluster2->status = TRUE;
78 |
79 | $entity_storage_interface = $this->prophesize(EntityStorageInterface::class);
80 | $entity_storage_interface->loadMultiple()
81 | ->willReturn([
82 | $cluster1->cluster_id => $cluster1,
83 | $cluster2->cluster_id => $cluster2,
84 | ]);
85 |
86 | $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
87 | $entity_type_manager->getStorage('elasticsearch_cluster')
88 | ->willReturn($entity_storage_interface->reveal());
89 |
90 | $this->clusterManager = new ClusterManager($state->reveal(), $entity_type_manager->reveal());
91 |
92 | $expected_clusters = [
93 | $cluster2->cluster_id => $cluster2,
94 | ];
95 | $this->assertEquals($expected_clusters, $this->clusterManager->loadAllClusters(FALSE));
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/tests/src/Unit/ElasticSearch/ClientManagerTest.php:
--------------------------------------------------------------------------------
1 | prophesize(ModuleHandlerInterface::class);
34 | $client_factory = $this->prophesize(ClientFactoryInterface::class);
35 | $client_factory->create(Argument::type('array'))
36 | ->willReturn($this->prophesize(ClientInterface::class)->reveal());
37 |
38 | $this->clientManager = new ClientManager($module_handler->reveal(), $client_factory->reveal());
39 | }
40 |
41 | /**
42 | * @covers ::__construct
43 | */
44 | public function testConstruct() {
45 | $this->assertInstanceOf(ClientManager::class, $this->clientManager);
46 | }
47 |
48 | /**
49 | * @covers ::getClientForCluster
50 | */
51 | public function testGetClientForCluster() {
52 | $cluster = $this->prophesize(Cluster::class);
53 | $cluster->getRawUrl()
54 | ->willReturn('http://example.com');
55 | $cluster = $cluster->reveal();
56 | $cluster->options['use_authentication'] = TRUE;
57 | $cluster->options['username'] = 'Tom';
58 | $cluster->options['password'] = 'Waits';
59 | $cluster->options['authentication_type'] = 'basic_auth';
60 |
61 | $client = $this->clientManager->getClientForCluster($cluster);
62 | $this->assertInstanceOf(ClientInterface::class, $client);
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/tests/src/Unit/ElasticSearch/Parameters/Builder/SearchBuilderTest.php:
--------------------------------------------------------------------------------
1 | prophesize(IndexInterface::class);
31 | $query = $this->prophesize(QueryInterface::class);
32 | $query->getIndex()
33 | ->willReturn($index->reveal());
34 |
35 | $this->searchBuilder = new SearchBuilder($query->reveal());
36 | }
37 |
38 | /**
39 | * @covers ::__construct
40 | */
41 | public function testConstruct() {
42 | $this->assertInstanceOf(SearchBuilder::class, $this->searchBuilder);
43 | }
44 |
45 | /**
46 | * @covers ::build
47 | */
48 | public function testBuild() {
49 | // TODO Can't test because IndexFactory is hardcoded
50 | // instead of injected so it can't be mocked.
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/src/Unit/ElasticSearch/Parameters/Factory/FilterFactoryTest.php:
--------------------------------------------------------------------------------
1 | prophesize(Condition::class);
27 |
28 | $condition->getValue()
29 | ->willReturn(FALSE);
30 |
31 | $condition->getOperator()
32 | ->willReturn('<>');
33 |
34 | $condition->getField()
35 | ->willReturn('foo');
36 |
37 | $filter = FilterFactory::filterFromCondition($condition->reveal());
38 | $expected_filter = [
39 | 'exists' =>
40 | [
41 | 'field' => 'foo',
42 | ],
43 | ];
44 | $this->assertEquals($expected_filter, $filter);
45 |
46 | // Thest the = operator.
47 | /** @var \Prophecy\Prophecy\ObjectProphecy $condition */
48 | $condition = $this->prophesize(Condition::class);
49 |
50 | $condition->getValue()
51 | ->willReturn(FALSE);
52 |
53 | $condition->getOperator()
54 | ->willReturn('=');
55 |
56 | $condition->getField()
57 | ->willReturn('foo');
58 |
59 | $filter = FilterFactory::filterFromCondition($condition->reveal());
60 | $expected_filter = [
61 | 'bool' => [
62 | 'must_not' => [
63 | 'exists' => ['field' => 'foo'],
64 | ],
65 | ],
66 | ];
67 | $this->assertEquals($expected_filter, $filter);
68 |
69 | // Other operators will throw an exception.
70 | /** @var \Prophecy\Prophecy\ObjectProphecy $condition */
71 | $condition = $this->prophesize(Condition::class);
72 |
73 | $condition->getValue()
74 | ->willReturn(FALSE);
75 |
76 | $condition->getOperator()
77 | ->willReturn('>');
78 |
79 | $condition->getField()
80 | ->willReturn('foo');
81 |
82 | $this->setExpectedException(\Exception::class, 'Incorrect filter criteria');
83 | FilterFactory::filterFromCondition($condition->reveal());
84 | }
85 |
86 | /**
87 | * @covers ::filterFromCondition
88 | */
89 | public function testFilterFromConditionB() {
90 | // Normal filters
91 | /** @var \Prophecy\Prophecy\ObjectProphecy $condition */
92 | $condition = $this->prophesize(Condition::class);
93 |
94 | $condition->getValue()
95 | ->willReturn('bar');
96 |
97 | $condition->getOperator()
98 | ->willReturn('=');
99 |
100 | $condition->getField()
101 | ->willReturn('foo');
102 |
103 | $filter = FilterFactory::filterFromCondition($condition->reveal());
104 | $expected_filter = [
105 | 'term' => [
106 | 'foo' => 'bar'
107 | ],
108 | ];
109 | $this->assertEquals($expected_filter, $filter);
110 |
111 | /** @var \Prophecy\Prophecy\ObjectProphecy $condition */
112 | $condition = $this->prophesize(Condition::class);
113 |
114 | $condition->getValue()
115 | ->willReturn(['bar', 'baz']);
116 |
117 | $condition->getOperator()
118 | ->willReturn('IN');
119 |
120 | $condition->getField()
121 | ->willReturn('foo');
122 |
123 | $filter = FilterFactory::filterFromCondition($condition->reveal());
124 | $expected_filter = [
125 | 'terms' => [
126 | 'foo' => ['bar', 'baz'],
127 | ],
128 | ];
129 | $this->assertEquals($expected_filter, $filter);
130 |
131 | /** @var \Prophecy\Prophecy\ObjectProphecy $condition */
132 | $condition = $this->prophesize(Condition::class);
133 |
134 | $condition->getValue()
135 | ->willReturn('bar');
136 |
137 | $condition->getOperator()
138 | ->willReturn('<>');
139 |
140 | $condition->getField()
141 | ->willReturn('foo');
142 |
143 | $filter = FilterFactory::filterFromCondition($condition->reveal());
144 | $expected_filter = [
145 | 'not' => [
146 | 'filter' =>[
147 | 'term' => ['foo' => 'bar'],
148 | ],
149 | ],
150 | ];
151 | $this->assertEquals($expected_filter, $filter);
152 |
153 | /** @var \Prophecy\Prophecy\ObjectProphecy $condition */
154 | $condition = $this->prophesize(Condition::class);
155 |
156 | $condition->getValue()
157 | ->willReturn(1);
158 |
159 | $condition->getOperator()
160 | ->willReturn('>');
161 |
162 | $condition->getField()
163 | ->willReturn('foo');
164 |
165 | $filter = FilterFactory::filterFromCondition($condition->reveal());
166 | $expected_filter = [
167 | 'range' => [
168 | 'foo' => [
169 | 'from' => 1,
170 | 'to' => NULL,
171 | 'include_lower' => FALSE,
172 | 'include_upper' => FALSE,
173 | ],
174 | ],
175 | ];
176 | $this->assertEquals($expected_filter, $filter);
177 |
178 | /** @var \Prophecy\Prophecy\ObjectProphecy $condition */
179 | $condition = $this->prophesize(Condition::class);
180 |
181 | $condition->getValue()
182 | ->willReturn(1);
183 |
184 | $condition->getOperator()
185 | ->willReturn('>=');
186 |
187 | $condition->getField()
188 | ->willReturn('foo');
189 |
190 | $filter = FilterFactory::filterFromCondition($condition->reveal());
191 | $expected_filter = [
192 | 'range' => [
193 | 'foo' => [
194 | 'from' => 1,
195 | 'to' => NULL,
196 | 'include_lower' => TRUE,
197 | 'include_upper' => FALSE,
198 | ],
199 | ],
200 | ];
201 | $this->assertEquals($expected_filter, $filter);
202 |
203 | /** @var \Prophecy\Prophecy\ObjectProphecy $condition */
204 | $condition = $this->prophesize(Condition::class);
205 |
206 | $condition->getValue()
207 | ->willReturn(1);
208 |
209 | $condition->getOperator()
210 | ->willReturn('<');
211 |
212 | $condition->getField()
213 | ->willReturn('foo');
214 |
215 | $filter = FilterFactory::filterFromCondition($condition->reveal());
216 | $expected_filter = [
217 | 'range' => [
218 | 'foo' => [
219 | 'from' => NULL,
220 | 'to' => 1,
221 | 'include_lower' => FALSE,
222 | 'include_upper' => FALSE,
223 | ],
224 | ],
225 | ];
226 | $this->assertEquals($expected_filter, $filter);
227 |
228 | /** @var \Prophecy\Prophecy\ObjectProphecy $condition */
229 | $condition = $this->prophesize(Condition::class);
230 |
231 | $condition->getValue()
232 | ->willReturn(1);
233 |
234 | $condition->getOperator()
235 | ->willReturn('<=');
236 |
237 | $condition->getField()
238 | ->willReturn('foo');
239 |
240 | $filter = FilterFactory::filterFromCondition($condition->reveal());
241 | $expected_filter = [
242 | 'range' => [
243 | 'foo' => [
244 | 'from' => NULL,
245 | 'to' => 1,
246 | 'include_lower' => FALSE,
247 | 'include_upper' => TRUE,
248 | ],
249 | ],
250 | ];
251 | $this->assertEquals($expected_filter, $filter);
252 |
253 | // Other operators will throw an exception.
254 | /** @var \Prophecy\Prophecy\ObjectProphecy $condition */
255 | $condition = $this->prophesize(Condition::class);
256 |
257 | $condition->getValue()
258 | ->willReturn(FALSE);
259 |
260 | $condition->getOperator()
261 | ->willReturn('other-operator');
262 |
263 | $condition->getField()
264 | ->willReturn('foo');
265 |
266 | $this->setExpectedException(\Exception::class, 'Incorrect filter criteria');
267 | FilterFactory::filterFromCondition($condition->reveal());
268 | }
269 |
270 | }
271 |
--------------------------------------------------------------------------------
/tests/src/Unit/ElasticSearch/Parameters/Factory/IndexFactoryTest.php:
--------------------------------------------------------------------------------
1 | prophesize(FieldInterface::class);
22 | $field->getType()
23 | ->willReturn('text');
24 | $field->getBoost()
25 | ->willReturn(1);
26 |
27 | $expected_mapping = [
28 | 'type' => 'text',
29 | 'boost' => 1,
30 | 'fields' => [
31 | "keyword" => [
32 | "type" => 'keyword',
33 | 'ignore_above' => 256,
34 | ]
35 | ]
36 | ];
37 | $this->assertEquals($expected_mapping, MappingFactory::mappingFromField($field->reveal()));
38 |
39 | /** @var \Prophecy\Prophecy\ObjectProphecy $field_prophecy */
40 | $field = $this->prophesize(FieldInterface::class);
41 | $field->getType()
42 | ->willReturn('uri');
43 |
44 | $expected_mapping = [
45 | 'type' => 'keyword',
46 | ];
47 | $this->assertEquals($expected_mapping, MappingFactory::mappingFromField($field->reveal()));
48 |
49 | /** @var \Prophecy\Prophecy\ObjectProphecy $field_prophecy */
50 | $field = $this->prophesize(FieldInterface::class);
51 | $field->getType()
52 | ->willReturn('integer');
53 |
54 | $expected_mapping = [
55 | 'type' => 'integer',
56 | ];
57 | $this->assertEquals($expected_mapping, MappingFactory::mappingFromField($field->reveal()));
58 |
59 | /** @var \Prophecy\Prophecy\ObjectProphecy $field_prophecy */
60 | $field = $this->prophesize(FieldInterface::class);
61 | $field->getType()
62 | ->willReturn('boolean');
63 |
64 | $expected_mapping = [
65 | 'type' => 'boolean',
66 | ];
67 | $this->assertEquals($expected_mapping, MappingFactory::mappingFromField($field->reveal()));
68 |
69 | /** @var \Prophecy\Prophecy\ObjectProphecy $field_prophecy */
70 | $field = $this->prophesize(FieldInterface::class);
71 | $field->getType()
72 | ->willReturn('decimal');
73 |
74 | $expected_mapping = [
75 | 'type' => 'float',
76 | ];
77 | $this->assertEquals($expected_mapping, MappingFactory::mappingFromField($field->reveal()));
78 |
79 | /** @var \Prophecy\Prophecy\ObjectProphecy $field_prophecy */
80 | $field = $this->prophesize(FieldInterface::class);
81 | $field->getType()
82 | ->willReturn('date');
83 |
84 | $expected_mapping = [
85 | 'type' => 'date',
86 | 'format' => 'epoch_second',
87 | ];
88 | $this->assertEquals($expected_mapping, MappingFactory::mappingFromField($field->reveal()));
89 |
90 | /** @var \Prophecy\Prophecy\ObjectProphecy $field_prophecy */
91 | $field = $this->prophesize(FieldInterface::class);
92 | $field->getType()
93 | ->willReturn('attachment');
94 |
95 | $expected_mapping = [
96 | 'type' => 'attachment',
97 | ];
98 | $this->assertEquals($expected_mapping, MappingFactory::mappingFromField($field->reveal()));
99 |
100 | /** @var \Prophecy\Prophecy\ObjectProphecy $field_prophecy */
101 | $field = $this->prophesize(FieldInterface::class);
102 | $field->getType()
103 | ->willReturn('object');
104 |
105 | $expected_mapping = [
106 | 'type' => 'nested',
107 | ];
108 | $this->assertEquals($expected_mapping, MappingFactory::mappingFromField($field->reveal()));
109 |
110 | /** @var \Prophecy\Prophecy\ObjectProphecy $field_prophecy */
111 | $field = $this->prophesize(FieldInterface::class);
112 | $field->getType()
113 | ->willReturn('foo');
114 |
115 | $this->assertNull(MappingFactory::mappingFromField($field->reveal()));
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/tests/src/Unit/Entity/ClusterRouteProviderTest.php:
--------------------------------------------------------------------------------
1 | prophesize(EntityTypeInterface::class);
22 |
23 | /** @var \Symfony\Component\Routing\RouteCollection $route_collection */
24 | $route_collection = $cluster_route_provider->getRoutes($entity_type->reveal());
25 | $this->assertEquals(4, $route_collection->count());
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/tests/src/Unit/Entity/IndexRouteProviderTest.php:
--------------------------------------------------------------------------------
1 | prophesize(EntityTypeInterface::class);
22 |
23 | /** @var \Symfony\Component\Routing\RouteCollection $route_collection */
24 | $route_collection = $cluster_route_provider->getRoutes($entity_type->reveal());
25 | $this->assertEquals(2, $route_collection->count());
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/tests/src/Unit/Entity/IndexTest.php:
--------------------------------------------------------------------------------
1 | 'foo'], 'elasticsearch_index');
20 | $this->assertEquals('foo', $index->id());
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------