Commit 3bd4bf51 authored by Robert Lyon's avatar Robert Lyon Committed by Gerrit Code Review

Merge changes from topic 'elasticsearch 5'

* changes:
  Bug 1740208: Fetch related view for artefacts we can see
  Bug 1740207: Removing obsolete thumbnail images for elasticsearch
  Bug 1738898: Elasticsearch not indexing access changes correctly
  Bug 1734006: Allow the search facet tab links to scroll to tab area
  Bug 1734006: Elasticsearch to work with either elasticsearch 5+ or 6+ server
  Bug 1732565: Allow for faster indexing of large sites into elasticsearch
  Bug 1730530: Elasticsearch use different auths for reading from/writing to index
parents c9bef8c6 73a105bd
<?php
/**
*
* @package mahara
* @subpackage core
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
define('INTERNAL', 1);
define('CLI', 1);
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
require_once(get_config('docroot') . 'auth/lib.php');
require(get_config('libroot') . 'cli.php');
$cli = get_cli();
$options = array();
$settings = (object) array(
'info' => get_string('cli_fast_index', 'admin'),
'options' => $options,
);
$cli->setup($settings);
// First check that there isn't an elasticsearch cron indexing the site
if (get_record('config', 'field', '_cron_lock_search_elasticsearch_cron')) {
$cli->cli_exit(get_string('indexingrunning', 'search.elasticsearch'), true);
}
// Set the elasticsearch cron nextrun to null
if (!update_record('search_cron', array('nextrun' => NULL), array('plugin' => 'elasticsearch', 'callfunction' => 'cron'))) {
$cli->cli_exit(get_string('cli_unabletoupdatecron', 'admin'), true);
}
if ($total = count_records_sql("SELECT COUNT(*) FROM {search_elasticsearch_queue} WHERE status != ?", array(2))) {
$chunk = get_config_plugin('search', 'elasticsearch', 'cronlimit');
$runs = ceil($total/$chunk);
$cli->cli_print('count: ' . $total, true);
$cli->cli_print('limit: ' . $chunk, true);
$cli->cli_print('runs: ' . $runs, true);
if ($runs > 1) {
// only go fast if it's worth it - because we've reset the nextrun it
// will finish reindexing within 1 minute anyway.
$path = 'php ' . get_config('docroot') . 'lib/cron.php';
$cli->cli_print('path: ' . $path);
while ($runs > 0) {
passthru($path, $ret);
if ($ret !==0) {
$cli->cli_exit(get_string('cli_problemindexing', 'admin'), true);
}
$runs--;
update_record('search_cron', array('nextrun' => NULL), array('plugin' => 'elasticsearch', 'callfunction' => 'cron'));
$cli->cli_print('runs: ' . $runs, true);
}
}
}
$cli->cli_exit(get_string('cli_done', 'admin'), true);
......@@ -1311,6 +1311,12 @@ $string['cli_restore_warning'] = '*** WARNING *** Unable to restore backup of "%
$string['cli_tmpdir_notwritable'] = 'The temporary upload directory "%s" is not writable.';
$string['cli_lang_branch'] = 'Mahara series version to fetch langpacks for series "%s"';
// Fast index for elasticsearch
$string['cli_fast_index'] = 'Elasticsearch fast indexer allows quicker indexing of sites by avoiding waiting for the next cron run. Instead it begins next run immediately after first finishes.';
$string['cli_unabletoupdatecron'] = 'Unable to update the search cron database record';
$string['cli_problemindexing'] = 'A problem occurred while indexing';
$string['cli_done'] = 'Indexing finished';
$string['withselectedcontentexport'] = 'Re-queue items into the export queue';
$string['withselectedcontentdelete'] = 'Delete selected items from the export queue';
$string['allothers'] = 'All others';
......
......@@ -494,21 +494,31 @@ $cfg->cleanurlusereditable = true;
* See the helpfiles on the plugin's configuration page for details.
* @global string $cfg->plugin_search_elasticsearch_host
* @global int $cfg->plugin_search_elasticsearch_port
* @global int $cfg->plugin_search_elasticsearch_scheme
* @global string $cfg->plugin_search_elasticsearch_username
* @global string $cfg->plugin_search_elasticsearch_password
* @global string $cfg->plugin_search_elasticsearch_indexingusername
* @global string $cfg->plugin_search_elasticsearch_indexingpassword
* @global string $cfg->plugin_search_elasticsearch_indexname
* @global string $cfg->plugin_search_elasticsearch_bypassindexname
* @global string $cfg->plugin_search_elasticsearch_analyzer
* @global string $cfg->plugin_search_elasticsearch_types
* @global string $cfg->plugin_search_elasticsearch_ignoressl
*/
// $cfg->plugin_search_elasticsearch_host = '127.0.0.1';
// $cfg->plugin_search_elasticsearch_port = 9200;
// $cfg->plugin_search_elasticsearch_scheme = 'https';
// $cfg->plugin_search_elasticsearch_username = '';
// $cfg->plugin_search_elasticsearch_password = '';
// $cfg->plugin_search_elasticsearch_indexingusername = '';
// $cfg->plugin_search_elasticsearch_indexingpassword = '';
// $cfg->plugin_search_elasticsearch_indexname = 'mahara';
// $cfg->plugin_search_elasticsearch_bypassindexname = null;
// $cfg->plugin_search_elasticsearch_analyzer = 'mahara_analyzer';
// $cfg->plugin_search_elasticsearch_types = 'usr,interaction_instance,interaction_forum_post,group,view,artefact,block_instance,collection';
// When scheme is set to https but the mahara site is not in production mode this flag will
// allow system to ignore checking SSL certificate. Useful if testing with a site using a self-signed certificate
// $cfg->plugin_search_elasticsearch_ignoressl = false;
/**
* @global int $cfg->plugin_search_elasticsearch_requestlimit How many items to send per elasticsearch bulk request
* The main side effect of raising this, is that it increases the size of the POST request you send to your
......
......@@ -1295,7 +1295,13 @@ class View {
$beforeusers = activity_get_viewaccess_users($this->get('id'));
$select = 'view = ? AND visible = 1 AND token IS NULL';
$beforerules = get_records_select_array('view_access', $select, array($this->id));
if (get_config('searchplugin') == 'elasticsearch' && !empty($beforerules) && empty($accessdata) && $viewids != null) {
// We are removing access rules and none are left so we need to let elasticsearch know
// as it won't be picked up by the add_to_queue_access() function
safe_require('search', 'elasticsearch');
ElasticsearchIndexing::add_to_queue_access(null, null, $viewids);
}
db_begin();
delete_records_select('view_access', $select, array($this->id));
......
<!-- @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later -->
<!-- @copyright For copyright information on Mahara, please see the README file distributed with this software. -->
<h3>Auth password for writing to index</h3>
<p>If your Elasticsearch server requires a different HTTP basic auth username and password to write to index, you may provide the <strong>password</strong> here. <strong>Note:</strong> The core Elasticsearch software itself does not provide username or password features, but you may achieve this functionality by using a plugin, such as Search Guard, or by accessing it through a proxy.</p>
<p>This setting cannot be changed through the web interface. You may change it by setting a value in your config.php file for the following config variable:</p>
<p><code>$cfg->plugin_search_elasticsearch_password</code>.</p>
<!-- @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later -->
<!-- @copyright For copyright information on Mahara, please see the README file distributed with this software. -->
<h3>Auth username for writing to index</h3>
<p>If your Elasticsearch server requires a different HTTP basic auth username and password to write to index, you may provide the <strong>indexingusername</strong> here. <strong>Note:</strong> The core Elasticsearch software itself does not provide username or password features, but you may achieve this functionality by using a plugin, such as Search Guard, or by accessing it through a proxy.</p>
<p>This setting cannot be changed through the web interface. You may change it by setting a value in your config.php file for the following config variable:</p>
<p><code>$cfg->plugin_search_elasticsearch_indexingusername</code>.</p>
......@@ -2,6 +2,6 @@
<!-- @copyright For copyright information on Mahara, please see the README file distributed with this software. -->
<h3>Auth password</h3>
<p>If your Elasticsearch server requires an HTTP basic auth username and password to access it, you may provide the <strong>password</strong> here. <strong>Note:</strong> The core Elasticsearch software itself does not provide username or password features, but you may achieve this functionality by using a plugin or by accessing it through a proxy.</p>
<p>If your Elasticsearch server requires an HTTP basic auth username and password to access it, you may provide the <strong>password</strong> here. <strong>Note:</strong> The core Elasticsearch software itself does not provide username or password features, but you may achieve this functionality by using a plugin, such as Search guard, or by accessing it through a proxy.</p>
<p>This setting cannot be changed through the web interface. You may change it by setting a value in your config.php file for the following config variable:</p>
<p><code>$cfg->plugin_search_elasticsearch_password</code>.</p>
<!-- @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later -->
<!-- @copyright For copyright information on Mahara, please see the README file distributed with this software. -->
<h3>Elasticsearch scheme</h3>
<p>The scheme of the Elasticsearch server.</p>
<p>This setting cannot be changed through the web interface. You may change it by setting a value in your config.php file for the following config variable:</p>
<p><code>$cfg->plugin_search_elasticsearch_scheme</code>.</p>
......@@ -2,6 +2,6 @@
<!-- @copyright For copyright information on Mahara, please see the README file distributed with this software. -->
<h3>Auth username</h3>
<p>If your Elasticsearch server requires an HTTP basic auth username and password to access it, you may provide the <strong>username</strong> here. <strong>Note:</strong> The core Elasticsearch software itself does not provide username or password features, but you may achieve this functionality by using a plugin or by accessing it through a proxy.</p>
<p>If your Elasticsearch server requires an HTTP basic auth username and password to access it, you may provide the <strong>username</strong> here. <strong>Note:</strong> The core Elasticsearch software itself does not provide username or password features, but you may achieve this functionality by using a plugin, such as Search Guard, or by accessing it through a proxy.</p>
<p>This setting cannot be changed through the web interface. You may change it by setting a value in your config.php file for the following config variable:</p>
<p><code>$cfg->plugin_search_elasticsearch_username</code>.</p>
......@@ -46,11 +46,16 @@ $string['forumtopic'] = 'Forum topic';
$string['Group'] = 'Group';
$string['host'] = 'Host';
$string['hostdescription'] = 'Hostname of the Elasticsearch server. Default is 127.0.0.1.';
$string['indexingusername'] = 'Auth write username';
$string['indexingusernamedescription'] = '(Optional) Username to pass to Elasticsearch via HTTP basic auth for writing to index if different from reading from index';
$string['indexingpassword'] = 'Auth write password';
$string['indexingpassworddescription'] = '(Optional) Password to pass to Elasticsearch via HTTP basic auth for writing to index if different from reading from index';
$string['indexingrunning'] = 'Indexing cron job is running. Please try again in a few minutes.';
$string['indexname'] = 'Index name';
$string['indexnamedescription'] = 'Name of the Elasticsearch index. Default is "mahara".';
$string['indexstatusok'] = 'The current index "%s" has status "green". Elasticsearch is running.';
$string['indexstatusbad'] = 'The current index "%s" has status "%s" and will need to be fixed up.';
$string['indexstatusunknown'] = 'The current index "%s" has status unknown due to HTTP response "%s".';
$string['license'] = 'License';
$string['Media'] = 'Media';
$string['newindextype'] = 'A new index type "%s" has been added to your elasticsearch settings. For this to take effect you will need to reindex your site';
......@@ -79,6 +84,8 @@ $string['resetallindexes'] = 'Reset ALL indexes';
$string['resetdescription'] = 'This table shows the number of records of each type currently in the queue to be sent to the Elasticsearch server. Items are sent to the Elasticsearch server each time the search plugin\'s cron task runs (every 5 minutes). Click on the button at the bottom to reset the search index, deleting all records and requeuing them.';
$string['resetlegend'] = 'Index reset';
$string['resume'] = 'Résumé';
$string['scheme'] = 'Scheme';
$string['schemedescription'] = 'Scheme of the Elasticsearch server. Default is http';
$string['shards'] = 'Shards';
$string['shardsdescription'] = 'The number of pieces (shards) of the index to be made.';
$string['sortby'] = 'Sort by';
......
This diff is collapsed.
<?php
class ElasticsearchType_block_instance extends ElasticsearchType {
public static $mappingconf = array (
// New style v6 mapping
public static $mappingconfv6 = array (
'type' => array(
'type' => 'keyword',
),
'mainfacetterm' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'secfacetterm' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'id' => array (
'type' => 'long',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'title' => array (
'type' => 'text',
'include_in_all' => TRUE
'copy_to' => 'catch_all'
),
'description' => array (
'type' => 'text',
'include_in_all' => TRUE
'copy_to' => 'catch_all'
),
// the owner can be owner (user), group, or institution
'owner' => array (
'type' => 'long',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'group' => array (
'type' => 'long',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'institution' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'access' => array (
'type' => 'object',
'include_in_all' => FALSE,
// public - logged - friends: if block_instance is visible to public or logged-in users
// if public or logged, the other properties are ignored
'properties' => array (
'general' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
// array of institutions that have access to the artefact
'institutions' => array (
'type' => 'keyword',
'copy_to' => 'institution',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
// array of groups that have access to the artefact - empty (all), member, admin, tutor
'groups' => array (
'type' => 'object',
'include_in_all' => FALSE,
'properties' => array (
'all' => array (
'type' => 'integer',
'index' => 'not_analyzed',
'copy_to' => 'group',
'include_in_all' => false
),
'admin' => array (
'type' => 'integer',
'index' => 'not_analyzed',
'copy_to' => 'group',
'include_in_all' => false
),
'member' => array (
'type' => 'integer',
'index' => 'not_analyzed',
'copy_to' => 'group',
'include_in_all' => false
),
'tutor' => array (
'type' => 'integer',
'index' => 'not_analyzed',
'copy_to' => 'group',
'include_in_all' => false
)
)
),
......@@ -94,9 +72,7 @@ class ElasticsearchType_block_instance extends ElasticsearchType {
),
'usrs' => array (
'type' => 'integer',
'index' => 'not_analyzed',
'copy_to' => 'usr',
'include_in_all' => false
),
'usr' => array (
'type' => 'integer'
......@@ -107,15 +83,13 @@ class ElasticsearchType_block_instance extends ElasticsearchType {
'ctime' => array (
'type' => 'date',
'format' => 'YYYY-MM-dd HH:mm:ss',
'include_in_all' => FALSE
),
// sort is the field that will be used to sort the results alphabetically
'sort' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
)
);
public static $mainfacetterm = 'Text';
public static $secfacetterm = 'Document';
public function __construct($data) {
......
<?php
class ElasticsearchType_collection extends ElasticsearchType {
public static $mappingconf = array (
// New style v6 mapping
public static $mappingconfv6 = array (
'type' => array(
'type' => 'keyword',
),
'mainfacetterm' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'secfacetterm' => array ( // set to Page - used in 2nd facet
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'id' => array (
'type' => 'long',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'name' => array (
'type' => 'text',
'include_in_all' => TRUE
'copy_to' => 'catch_all'
),
'description' => array (
'type' => 'text',
'include_in_all' => TRUE
'copy_to' => 'catch_all'
),
'tags' => array (
'type' => 'keyword',
'copy_to' => 'tag',
'include_in_all' => TRUE
'copy_to' => ['tag','catch_all']
),
'tag' => array (
'type' => 'keyword'
......@@ -35,69 +32,47 @@ class ElasticsearchType_collection extends ElasticsearchType {
// the owner can be owner (user), group, or institution
'owner' => array (
'type' => 'long',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'group' => array (
'type' => 'long',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'institution' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'access' => array (
'type' => 'object',
'include_in_all' => FALSE,
// public - loggedin - friends: if artefact is visible to public or logged-in users
// if public or logged, the other properties are ignored
'properties' => array (
'general' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
), // array of institutions that have access to the artefact
'institutions' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'copy_to' => 'institution',
'include_in_all' => false
),
'institution' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => false
),
// array of groups that have access to the artefact
'groups' => array (
'type' => 'object',
'include_in_all' => FALSE,
'properties' => array (
'all' => array (
'type' => 'integer',
'index' => 'not_analyzed',
'copy_to' => 'group',
'include_in_all' => false
),
'admin' => array (
'type' => 'integer',
'index' => 'not_analyzed',
'copy_to' => 'group',
'include_in_all' => false
),
'member' => array (
'type' => 'integer',
'index' => 'not_analyzed',
'copy_to' => 'group',
'include_in_all' => false
),
'tutor' => array (
'type' => 'integer',
'index' => 'not_analyzed',
'copy_to' => 'group',
'include_in_all' => false
)
)
),
......@@ -107,9 +82,7 @@ class ElasticsearchType_collection extends ElasticsearchType {
// array of user ids that have access to the artefact
'usrs' => array (
'type' => 'integer',
'index' => 'not_analyzed',
'copy_to' => 'usr',
'include_in_all' => false
),
'usr' => array (
'type' => 'integer'
......@@ -119,15 +92,13 @@ class ElasticsearchType_collection extends ElasticsearchType {
'ctime' => array (
'type' => 'date',
'format' => 'YYYY-MM-dd HH:mm:ss',
'include_in_all' => FALSE
),
// sort is the field that will be used to sort the results alphabetically
'sort' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
)
);
public static $mainfacetterm = 'Portfolio';
public static $secfacetterm = 'Collection';
public function __construct($data) {
......
<?php
class ElasticsearchType_event_log extends ElasticsearchType {
public static $mappingconf = array (
// New style v6 mapping
public static $mappingconfv6 = array (
'type' => array(
'type' => 'keyword',
),
'mainfacetterm' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'secfacetterm' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'id' => array (
'type' => 'long',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'usr' => array (
'type' => 'integer',
'include_in_all' => TRUE
'copy_to' => 'catch_all'
),
'realusr' => array (
'type' => 'integer',
'include_in_all' => TRUE
'copy_to' => 'catch_all'
),
'event' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => TRUE
'copy_to' => 'catch_all'
),
'data' => array (
'type' => 'text',
'include_in_all' => FALSE
),
'resourceid' => array (
'type' => 'integer',
'include_in_all' => FALSE
),
'resourcetype' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => TRUE
'copy_to' => 'catch_all'
),
'parentresourceid' => array (
'type' => 'integer',
'include_in_all' => FALSE
),
'parentresourcetype' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => TRUE
'copy_to' => 'catch_all'
),
'ownerid' => array (
'type' => 'integer',
'include_in_all' => FALSE
),
'ownertype' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'ctime' => array (
'type' => 'date',
'format' => 'YYYY-MM-dd HH:mm:ss',
'include_in_all' => FALSE
),
'yearweek' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'createdbyuser' => array (
'type' => 'boolean',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'firstname' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'lastname' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'username' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'displayname' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
);
public static $mainfacetterm = 'Event';
public static $secfacetterm = 'Log';
public function __construct($data) {
......@@ -200,8 +177,6 @@ class ElasticsearchType_event_log extends ElasticsearchType {
// 1 - Get the aggregate list of events
// ------------------------------------------------------------------------------------------
$records = array();
$searchfield = '_all';
$matching = array(
'match_all' => new \stdClass()
);
......
<?php
class ElasticsearchType_group extends ElasticsearchType {
public static $mappingconf = array (
// New style v6 mapping
public static $mappingconfv6 = array (
'type' => array(
'type' => 'keyword',
),
'mainfacetterm' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => TRUE
'copy_to' => 'catch_all'
),
'id' => array (
'type' => 'long',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'name' => array (
'type' => 'text',
'include_in_all' => TRUE
'copy_to' => 'catch_all'
),
'description' => array (
'type' => 'text',
'include_in_all' => TRUE
'copy_to' => 'catch_all'
),
'grouptype' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'jointype' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
// access to group
'access' => array (
'type' => 'object',
'include_in_all' => FALSE,
'properties' => array (
'general' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'groups' => array (
'type' => 'object',
'include_in_all' => FALSE,
'properties' => array (
'member' => array (
'type' => 'integer',
'index' => 'not_analyzed',
'copy_to' => 'group',
'include_in_all' => false
)
)
),
......@@ -59,15 +50,13 @@ class ElasticsearchType_group extends ElasticsearchType {
'ctime' => array (
'type' => 'date',
'format' => 'YYYY-MM-dd HH:mm:ss',
'include_in_all' => FALSE
),
// sort is the field that will be used to sort the results alphabetically
'sort' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
)
);
public static $mainfacetterm = 'Group';
public function __construct($data) {
$this->conditions = array (
......
<?php
class ElasticsearchType_interaction_forum_post extends ElasticsearchType {
public static $mappingconf = array (
// New style v6 mapping
public static $mappingconfv6 = array (
'type' => array(
'type' => 'keyword',
),
'mainfacetterm' => array (
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'secfacetterm' => array ( // set to Forumpost
'type' => 'keyword',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'id' => array (
'type' => 'long',
'index' => 'not_analyzed',
'include_in_all' => FALSE
),
'subject' => array (
'type' => 'keyword',
'include_in_all' => TRUE
'copy_to' => 'catch_all'
),