<commit_before>"use strict";

/* globals module, require */

var db = module.parent.require('./database'),
	winston = module.parent.require('winston'),
	elasticsearch = require('elasticsearch'),
	async = module.parent.require('async'),
	_ = module.parent.require('underscore'),

	LRU = require('lru-cache'),
	cache = LRU({ max: 20, maxAge: 1000 * 60 * 60 }),	// Remember the last 20 searches in the past hour

	topics = module.parent.require('./topics'),
	posts = module.parent.require('./posts'),

	escapeSpecialChars = function(s) {
		return s.replace(/([\+\-&\|!\(\)\{\}\[\]\^"~\*\?:\\\ ])/g, function(match) {
			return '\\' + match;
		});
	},

	Elasticsearch = {
		/*
			Defaults configs:
			host: localhost:9200
			enabled: undefined (false)
		*/
		config: {
			sniffOnStart: true,             // Should the client attempt to detect the rest of the cluster when it is first instantiated?
			sniffInterval: 60000,           // Every n milliseconds, perform a sniff operation and make sure our list of nodes is complete.
			sniffOnConnectionFault: true,   // Should the client immediately sniff for a more current list of nodes when a connection dies?
			host: 'localhost:9200',
			index_name: 'nodebb',
			post_type: 'posts'
		},	// default is localhost:9200
		client: undefined
	};

_.str = require('underscore.string'); // Import Underscore.string to separate object, because there are conflict functions (include, reverse, contains)
_.mixin(_.str.exports()); // Mix in non-conflict functions to Underscore namespace if you want

Elasticsearch.init = function(data, callback) {
	var pluginMiddleware = require('./middleware'),
		render = function(req, res, next) {
			// Regenerate csrf token
			var token = req.csrfToken();

			res.render('admin/plugins/elasticsearch', {
				ping: res.locals.ping,
				enabled: res.locals.enabled,
				stats: res.locals.stats,
				csrf: token
			});
		};

	data.router.get('/admin/plugins/elasticsearch', data.middleware.applyCSRF, data.middleware.admin.buildHeader, pluginMiddleware.ping, pluginMiddleware.getEnabled, pluginMiddleware.getStats, render);
	data.router.get('/api/admin/plugins/elasticsearch', data.middleware.applyCSRF, pluginMiddleware.ping, pluginMiddleware.getEnabled, pluginMiddleware.getStats, render);

	// Utility
	data.router.post('/admin/plugins/elasticsearch/rebuild', data.middleware.admin.isAdmin, Elasticsearch.rebuildIndex);
	data.router.post('/admin/plugins/elasticsearch/toggle', Elasticsearch.toggle);
	data.router.delete('/admin/plugins/elasticsearch/flush', data.middleware.admin.isAdmin, Elasticsearch.flush);

	Elasticsearch.getSettings(Elasticsearch.connect);

	callback();
};

Elasticsearch.ping = function(callback) {
	if (Elasticsearch.client) {
		Elasticsearch.client.ping(callback);
	} else {
		callback(new Error('not-connected'));
	}
};

Elasticsearch.checkConflict = function() {
	if (module.parent.exports.libraries['nodebb-plugin-dbsearch']) {
		return true;
	} else {
		return false;
	}
};

Elasticsearch.getNotices = function(notices, callback) {
	Elasticsearch.ping(function(err, obj) {
		var elasticsearchNotices = [
				{ done: !err ? true : false, doneText: 'Elasticsearch connection OK', notDoneText: 'Could not connect to Elasticsearch server' },
				{ done: parseInt(Elasticsearch.config.enabled, 10) || false, doneText: 'Elasticsearch Indexing Enabled', notDoneText: 'Elasticsearch Indexing Disabled' }
			];

		callback(null, notices.concat(elasticsearchNotices));
	})
};

Elasticsearch.getSettings = function(callback) {
	db.getObject('settings:elasticsearch', function(err, config) {
		Elasticsearch.config = {};
		if (!err) {
			for(var k in config) {
				if (config.hasOwnProperty(k) && config[k].length && !Elasticsearch.config.hasOwnProperty(k)) {
					Elasticsearch.config[k] = config[k];
				}
			}
		} else {
			winston.error('[plugin:elasticsearch] Could not fetch settings, assuming defaults.');
		}

		callback();
	});
};

Elasticsearch.getRecordCount = function(callback) {
	if (!Elasticsearch.client) {
		return callback(new Error('not-connected'));
	}

	Elasticsearch.client.count({
		index: Elasticsearch.config.index_name
	}, function (error, response) {
		if (!error && response) {
			callback(null, response.count);
		}
		else {
			callback(error, 0);
		}
	});
};

Elasticsearch.getTopicCount = function(callback) {
	if (!Elasticsearch.client) {
		return callback(new Error('not-connected'));
	}

	Elasticsearch.client.count({
		index: Elasticsearch.config.index_name,
		type: Elasticsearch.config.post_type,
		body: {
			query: {
				constant_score: {
					filter: {
						exists: {
							field: "title"
						}
					}
				}
			}
		}
	}, function (error, response) {
		if (!error && response) {
			callback(null, response.count);
		}
		else {
			callback(error, 0);
		}
	});
};

Elasticsearch.connect = function() {
	if (!Elasticsearch.config.host) {
		return;
	}

	if (Elasticsearch.client) {
		delete Elasticsearch.client;
	}

	// Convert host to array
	var hosts = Elasticsearch.config.host.split(',');
	hosts = _.map(hosts, function(host){ return _.trim(host); });

	// Compact array to remove empty elements just in case.
	hosts = _.compact(hosts);

	if (hosts.length === 0) {
		return;
	}

	Elasticsearch.config.hosts = hosts;

	// Now remove the host since we're going to use hosts.
	delete Elasticsearch.config.host;

	Elasticsearch.client = new elasticsearch.Client(Elasticsearch.config);
};

Elasticsearch.adminMenu = function(custom_header, callback) {
	custom_header.plugins.push({
		"route": '/plugins/elasticsearch',
		"icon": 'fa-search',
		"name": 'Elasticsearch'
	});

	callback(null, custom_header);
};

Elasticsearch.search = function(data, callback) {
	if (Elasticsearch.checkConflict()) {
		// The dbsearch plugin was detected, abort search!
		winston.warn('[plugin/elasticsearch] Another search plugin (dbsearch) is enabled, so search via Elasticsearch was aborted.');
		return callback(null, data);
	} else if (data.index === 'topic') {
		// We are only using the "post" index, because Elasticsearch does its own relevency sorting
		return callback(null, []);<commit_msg>Add check for conflict with solr plugin.<commit_after>"use strict";

/* globals module, require */

var db = module.parent.require('./database'),
	winston = module.parent.require('winston'),
	elasticsearch = require('elasticsearch'),
	async = module.parent.require('async'),
	_ = module.parent.require('underscore'),

	LRU = require('lru-cache'),
	cache = LRU({ max: 20, maxAge: 1000 * 60 * 60 }),	// Remember the last 20 searches in the past hour

	topics = module.parent.require('./topics'),
	posts = module.parent.require('./posts'),

	escapeSpecialChars = function(s) {
		return s.replace(/([\+\-&\|!\(\)\{\}\[\]\^"~\*\?:\\\ ])/g, function(match) {
			return '\\' + match;
		});
	},

	Elasticsearch = {
		/*
			Defaults configs:
			host: localhost:9200
			enabled: undefined (false)
		*/
		config: {
			sniffOnStart: true,             // Should the client attempt to detect the rest of the cluster when it is first instantiated?
			sniffInterval: 60000,           // Every n milliseconds, perform a sniff operation and make sure our list of nodes is complete.
			sniffOnConnectionFault: true,   // Should the client immediately sniff for a more current list of nodes when a connection dies?
			host: 'localhost:9200',
			index_name: 'nodebb',
			post_type: 'posts'
		},	// default is localhost:9200
		client: undefined
	};

_.str = require('underscore.string'); // Import Underscore.string to separate object, because there are conflict functions (include, reverse, contains)
_.mixin(_.str.exports()); // Mix in non-conflict functions to Underscore namespace if you want

Elasticsearch.init = function(data, callback) {
	var pluginMiddleware = require('./middleware'),
		render = function(req, res, next) {
			// Regenerate csrf token
			var token = req.csrfToken();

			res.render('admin/plugins/elasticsearch', {
				ping: res.locals.ping,
				enabled: res.locals.enabled,
				stats: res.locals.stats,
				csrf: token
			});
		};

	data.router.get('/admin/plugins/elasticsearch', data.middleware.applyCSRF, data.middleware.admin.buildHeader, pluginMiddleware.ping, pluginMiddleware.getEnabled, pluginMiddleware.getStats, render);
	data.router.get('/api/admin/plugins/elasticsearch', data.middleware.applyCSRF, pluginMiddleware.ping, pluginMiddleware.getEnabled, pluginMiddleware.getStats, render);

	// Utility
	data.router.post('/admin/plugins/elasticsearch/rebuild', data.middleware.admin.isAdmin, Elasticsearch.rebuildIndex);
	data.router.post('/admin/plugins/elasticsearch/toggle', Elasticsearch.toggle);
	data.router.delete('/admin/plugins/elasticsearch/flush', data.middleware.admin.isAdmin, Elasticsearch.flush);

	Elasticsearch.getSettings(Elasticsearch.connect);

	callback();
};

Elasticsearch.ping = function(callback) {
	if (Elasticsearch.client) {
		Elasticsearch.client.ping(callback);
	} else {
		callback(new Error('not-connected'));
	}
};

Elasticsearch.checkConflict = function() {
	if (module.parent.exports.libraries['nodebb-plugin-dbsearch'] || module.parent.exports.libraries['nodebb-plugin-solr']) {
		return true;
	} else {
		return false;
	}
};

Elasticsearch.getNotices = function(notices, callback) {
	Elasticsearch.ping(function(err, obj) {
		var elasticsearchNotices = [
				{ done: !err ? true : false, doneText: 'Elasticsearch connection OK', notDoneText: 'Could not connect to Elasticsearch server' },
				{ done: parseInt(Elasticsearch.config.enabled, 10) || false, doneText: 'Elasticsearch Indexing Enabled', notDoneText: 'Elasticsearch Indexing Disabled' }
			];

		callback(null, notices.concat(elasticsearchNotices));
	})
};

Elasticsearch.getSettings = function(callback) {
	db.getObject('settings:elasticsearch', function(err, config) {
		Elasticsearch.config = {};
		if (!err) {
			for(var k in config) {
				if (config.hasOwnProperty(k) && config[k].length && !Elasticsearch.config.hasOwnProperty(k)) {
					Elasticsearch.config[k] = config[k];
				}
			}
		} else {
			winston.error('[plugin:elasticsearch] Could not fetch settings, assuming defaults.');
		}

		callback();
	});
};

Elasticsearch.getRecordCount = function(callback) {
	if (!Elasticsearch.client) {
		return callback(new Error('not-connected'));
	}

	Elasticsearch.client.count({
		index: Elasticsearch.config.index_name
	}, function (error, response) {
		if (!error && response) {
			callback(null, response.count);
		}
		else {
			callback(error, 0);
		}
	});
};

Elasticsearch.getTopicCount = function(callback) {
	if (!Elasticsearch.client) {
		return callback(new Error('not-connected'));
	}

	Elasticsearch.client.count({
		index: Elasticsearch.config.index_name,
		type: Elasticsearch.config.post_type,
		body: {
			query: {
				constant_score: {
					filter: {
						exists: {
							field: "title"
						}
					}
				}
			}
		}
	}, function (error, response) {
		if (!error && response) {
			callback(null, response.count);
		}
		else {
			callback(error, 0);
		}
	});
};

Elasticsearch.connect = function() {
	if (!Elasticsearch.config.host) {
		return;
	}

	if (Elasticsearch.client) {
		delete Elasticsearch.client;
	}

	// Convert host to array
	var hosts = Elasticsearch.config.host.split(',');
	hosts = _.map(hosts, function(host){ return _.trim(host); });

	// Compact array to remove empty elements just in case.
	hosts = _.compact(hosts);

	if (hosts.length === 0) {
		return;
	}

	Elasticsearch.config.hosts = hosts;

	// Now remove the host since we're going to use hosts.
	delete Elasticsearch.config.host;

	Elasticsearch.client = new elasticsearch.Client(Elasticsearch.config);
};

Elasticsearch.adminMenu = function(custom_header, callback) {
	custom_header.plugins.push({
		"route": '/plugins/elasticsearch',
		"icon": 'fa-search',
		"name": 'Elasticsearch'
	});

	callback(null, custom_header);
};

Elasticsearch.search = function(data, callback) {
	if (Elasticsearch.checkConflict()) {
		// The dbsearch plugin was detected, abort search!
		winston.warn('[plugin/elasticsearch] Another search plugin (dbsearch or solr) is enabled, so search via Elasticsearch was aborted.');
		return callback(null, data);
	} else if (data.index === 'topic') {
		// We are only using the "post" index, because Elasticsearch does its own relevency sorting
		return callback(null, []);
{'fraction_non_alphanumeric': 0.14380782340529136, 'fraction_numerical': 0.0055185846453497805, 'mean_word_length': 4.175556488870223, 'pattern_counts': {'":': 6, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5763890', 'n_tokens_mistral': 4147, 'n_tokens_neox': 3834, 'n_words': 887}