/*
	Copyright 2019  qTranslate-XT  (https://github.com/qtranslate/qtranslate-xt)

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
 * Search for 'Designed as interface for other plugin integration' in comments to functions
 * to find out which functions are safe to use in the 3rd-party integration.
 * Avoid accessing internal variables directly, as they are subject to be re-designed at any time.
 * Single global variable 'qTranslateConfig' is an entry point to the interface.
 * - qTranslateConfig.qtx - is a shorthand reference to the only global object of type 'qTranslateX'.
 * - qTranslateConfig.js - is a place where custom Java script functions are stored, if needed.
 * Read Integration Guide: https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide for more information.
 */

// global config
var qTranslateConfig = window.qTranslateConfig;

/**
 * since 3.2.7
 */
qtranxj_get_split_blocks = function (text) {
	var split_regex = /(<!--:[a-z]{2}-->|<!--:-->|\[:[a-z]{2}]|\[:]|{:[a-z]{2}}|{:})/gi; // @since 3.3.6 swirly brackets
	return text.xsplit(split_regex);
};

/**
 * since 3.2.7
 */
qtranxj_split = function (text) {
	var blocks = qtranxj_get_split_blocks(text);
	return qtranxj_split_blocks(blocks);
};

/**
 * since 3.1-b1 - closing tag [:]
 */
qtranxj_split_blocks = function (blocks) {
	var result = new Object;
	for (var lang in qTranslateConfig.language_config) {
		result[lang] = '';
	}
	if (!blocks || !blocks.length)
		return result;
	if (blocks.length === 1) {
		// no language separator found, enter it to all languages
		var b = blocks[0];
		for (var lang in qTranslateConfig.language_config) {
			result[lang] += b;
		}
		return result;
	}
	var clang_regex = /<!--:([a-z]{2})-->/gi;
	var blang_regex = /\[:([a-z]{2})]/gi;
	var slang_regex = /{:([a-z]{2})}/gi; // @since 3.3.6 swirly brackets
	var lang = false;
	var matches;
	for (var i = 0; i < blocks.length; ++i) {
		var b = blocks[i];
		if (!b.length)
			continue;
		matches = clang_regex.exec(b);
		clang_regex.lastIndex = 0;
		if (matches != null) {
			lang = matches[1];
			continue;
		}
		matches = blang_regex.exec(b);
		blang_regex.lastIndex = 0;
		if (matches != null) {
			lang = matches[1];
			continue;
		}
		matches = slang_regex.exec(b);
		slang_regex.lastIndex = 0;
		if (matches != null) {
			lang = matches[1];
			continue;
		}
		if (b === '<!--:-->' || b === '[:]' || b === '{:}') {
			lang = false;
			continue;
		}
		if (lang) {
			if (!result[lang]) result[lang] = b;
			else result[lang] += b;
			lang = false;
		} else {
			// keep neutral text
			for (var key in result) {
				result[key] += b;
			}
		}
	}
	return result;
};

String.prototype.xsplit = function (_regEx) {
	// Most browsers can do this properly, so let them work, they'll do it faster
	if ('a~b'.split(/(~)/).length === 3) {
		return this.split(_regEx);
	}

	if (!_regEx.global) {
		_regEx = new RegExp(_regEx.source, 'g' + (_regEx.ignoreCase ? 'i' : ''));
	}

	// IE (and any other browser that can't capture the delimiter)
	// will, unfortunately, have to be slowed down
	var start = 0, arr = [];
	var result;
	while ((result = _regEx.exec(this)) != null) {
		arr.push(this.slice(start, result.index));
		if (result.length > 1)
			arr.push(result[1]);
		start = _regEx.lastIndex;
	}
	if (start < this.length)
		arr.push(this.slice(start));
	if (start === this.length)
		arr.push(''); // delim at the end
	return arr;
};

function qtranxj_ce(tagName, props, pNode, isFirst) {
	var el = document.createElement(tagName);
	if (props) {
		for (var prop in props) {
			el[prop] = props[prop];
		}
	}
	if (pNode) {
		if (isFirst && pNode.firstChild) {
			pNode.insertBefore(el, pNode.firstChild);
		}
		else {
			pNode.appendChild(el);
		}
	}
	return el;
}

(function ($) {
	// the edit language corresponds to the current LSB selection or the main admin language for single mode
	var keyEditLanguage = 'qtranslate-xt-admin-edit-language';
	var storeEditLanguage = function (lang) {
		try {
			sessionStorage.setItem(keyEditLanguage, lang);
		}
		catch (e) {
			// no big deal if this can't be stored
			console.log('Failed to store "' + keyEditLanguage + '" with sessionStorage', e);
		}
	};

	var qTranslateX = function (pg) {
		var qtx = this;

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 * return array keyed by two-letter language code. Example of usage:
		 * var langs = getLanguages();
		 * for(var lang_code in langs){
		 *  var lang_conf = langs[lang_code];
		 *  // variables available:
		 *  //lang_conf.name //name of language in native language
		 *  //lang_conf.admin_name //in the admin language chosen
		 *  //lang_conf.flag
		 *  //lang_conf.locale
		 *  // and may be more properties later
		 * }
		 * @since 3.3
		 */
		this.getLanguages = function () {
			return qTranslateConfig.language_config;
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 * return URL to folder with flag images.
		 */
		this.getFlagLocation = function () {
			return qTranslateConfig.flag_location;
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 * return true if 'lang' is in the hash of enabled languages.
		 * This function maybe needed, as function qtranxj_split may return languages,
		 * which are not enabled, in case they were previously enabled and had some data.
		 * Such data is preserved and re-saved until user deletes it manually.
		 */
		this.isLanguageEnabled = function (lang) {
			return !!qTranslateConfig.language_config[lang];
		};

		if (qTranslateConfig.LSB) {
			qTranslateConfig.activeLanguage = sessionStorage.getItem(keyEditLanguage);
			if (!qTranslateConfig.activeLanguage || !this.isLanguageEnabled(qTranslateConfig.activeLanguage)) {
				qTranslateConfig.activeLanguage = qTranslateConfig.language;
				if (this.isLanguageEnabled(qTranslateConfig.activeLanguage)) {
					storeEditLanguage(qTranslateConfig.activeLanguage);
				} else {
					// fallback to single mode
					qTranslateConfig.LSB = false;
				}
			}
		} else {
			qTranslateConfig.activeLanguage = qTranslateConfig.language;
			// no need to store for the current mode, but just in case the LSB are used later
			storeEditLanguage(qTranslateConfig.activeLanguage);
		}

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 * @since 3.3
		 */
		this.getActiveLanguage = function () {
			return qTranslateConfig.activeLanguage;
		};

		var contentHooks = {};

		var updateFusedValueH = function (id, value) {
			if (qTranslateConfig.RAW)
				return;
			var h = contentHooks[id];
			var text = value.trim();
			h.fields[h.lang].value = text;
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 * @since 3.3.4
		 */
		this.hasContentHook = function (id) {
			return contentHooks[id];
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 * @since 3.3.2
		 */
		this.addContentHook = function (inpField, encode, field_name) {
			if (!inpField) return false;
			switch (inpField.tagName) {
				case 'TEXTAREA':
					break;
				case 'INPUT':
					// reject the types which cannot be multilingual
					switch (inpField.type) {
						case 'button':
						case 'checkbox':
						case 'password':
						case 'radio':
						case 'submit':
							return false;
					}
					break;
				default:
					return false;
			}

			if (!field_name) {
				if (!inpField.name) return false;
				field_name = inpField.name;
			}
			if (inpField.id) {
				if (contentHooks[inpField.id]) {
					if ($.contains(document, inpField))
						return contentHooks[inpField.id];
					// otherwise some Java script already removed previously hooked element
					qtx.removeContentHook(inpField);
				}
			} else if (!contentHooks[field_name]) {
				inpField.id = field_name;
			} else {
				var idx = 0;
				do {
					++idx;
					inpField.id = field_name + idx;
				} while (contentHooks[inpField.id]);
			}

			/**
			 * Highlighting the translatable fields
			 * @since 3.2-b3
			 */
			inpField.className += ' qtranxs-translatable';

			var h = contentHooks[inpField.id] = {};
			h.name = field_name;
			h.contentField = inpField;
			h.lang = qTranslateConfig.activeLanguage;

			var qtx_prefix;
			if (encode) {
				switch (encode) {
					case 'slug':
						qtx_prefix = 'qtranslate-slugs[';
						break;
					case 'term':
						qtx_prefix = 'qtranslate-terms[';
						break;
					default:
						qtx_prefix = 'qtranslate-fields[';
						break;
				}
			} else {
				// since 3.1 we get rid of <--:--> encoding
				encode = '[';
				qtx_prefix = 'qtranslate-fields[';
			}

			h.encode = encode;

			var bfnm, sfnm, p = h.name.indexOf('[');
			if (p < 0) {
				bfnm = qtx_prefix + h.name + ']';
			} else {
				bfnm = qtx_prefix + h.name.substring(0, p) + ']';
				if (h.name.lastIndexOf('[]') < 0) {
					bfnm += h.name.substring(p);
				} else {
					var len = h.name.length - 2;
					if (len > p)
						bfnm += h.name.substring(p, len);
					sfnm = '[]';
				}
			}

			var contents;

			h.fields = {};
			if (!qTranslateConfig.RAW) {
				contents = qtranxj_split(inpField.value);
				inpField.value = contents[h.lang];
				for (var lang in contents) {
					var text = contents[lang];
					var fnm = bfnm + '[' + lang + ']';
					if (sfnm)
						fnm += sfnm;
					var f = qtranxj_ce('input', {name: fnm, type: 'hidden', className: 'hidden', value: text});
					h.fields[lang] = f;
					inpField.parentNode.insertBefore(f, inpField);
				}

				// insert a hidden element in the form so that the edit language is sent to the server
				$form = $(inpField).closest('form');
				if ($form.length) {
					var $hidden = $form.find('input[name="qtranslate-edit-language"]');
					if (!$hidden.length) {
						qtranxj_ce('input', {
							type: 'hidden',
							name: 'qtranslate-edit-language',
							value: qTranslateConfig.activeLanguage
						}, $form[0], true);
					}
				}
				else {
					console.error('No form found for translatable field id=', inpField.id);
				}
			}

			// since 3.2.9.8 - h.contents -> h.fields
			// since 3.3.8.7 - slug & term
			switch (encode) {
				case 'slug':
				case 'term': {
					if (qTranslateConfig.RAW)
						contents = qtranxj_split(inpField.value);
					h.sepfield = qtranxj_ce('input', {
						name: bfnm + '[qtranslate-original-value]',
						type: 'hidden',
						className: 'hidden',
						value: contents[qTranslateConfig.default_language]
					});
				}
					break;
				default: {
					if (!qTranslateConfig.RAW) {
						h.sepfield = qtranxj_ce('input', {
							name: bfnm + '[qtranslate-separator]',
							type: 'hidden',
							className: 'hidden',
							value: encode
						});
					}
				}
					break;
			}

			if (h.sepfield)
				inpField.parentNode.insertBefore(h.sepfield, inpField);

			return h;
		};
		this.addContentHookC = function (inpField) {
			return qtx.addContentHook(inpField, '['); // TODO shouldn't it be '<' ?!
		};
		this.addContentHookB = function (inpField) {
			return qtx.addContentHook(inpField, '[');
		};

		this.addContentHookById = function (id, sep, nm) {
			return qtx.addContentHook(document.getElementById(id), sep, nm);
		};
		this.addContentHookByIdName = function (nm) {
			var sep;
			switch (nm[0]) {
				case '<':
				case '[':
					sep = nm.substring(0, 1);
					nm = nm.substring(1);
					break;
				default:
					break;
			}
			return qtx.addContentHookById(nm, sep);
		};
		this.addContentHookByIdC = function (id) {
			return qtx.addContentHookById(id, '['); // TODO shouldn't it be '<' ?!
		};
		this.addContentHookByIdB = function (id) {
			return qtx.addContentHookById(id, '[');
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 * @since 3.1-b2
		 */
		this.addContentHooks = function (fields, sep, field_name) {
			for (var i = 0; i < fields.length; ++i) {
				var field = fields[i];
				qtx.addContentHook(field, sep, field_name);
			}
		};

		var addContentHooksByClassName = function (nm, container, sep) {
			if (!container)
				container = document;
			var fields = container.getElementsByClassName(nm);
			qtx.addContentHooks(fields, sep);
		};

		this.addContentHooksByClass = function (nm, container) {
			var sep;
			if (nm.indexOf('<') === 0 || nm.indexOf('[') === 0) {
				sep = nm.substring(0, 1);
				nm = nm.substring(1);
			}
			addContentHooksByClassName(nm, container, sep);
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 * @since 3.3.2
		 */
		this.addContentHooksByTagInClass = function (nm, tag, container) {
			var elems = container.getElementsByClassName(nm);
			for (var i = 0; i < elems.length; ++i) {
				var elem = elems[i];
				var items = elem.getElementsByTagName(tag);
				qtx.addContentHooks(items);
			}
		};

		var removeContentHookH = function (h) {
			if (!h)
				return false;
			if (h.sepfield)
				$(h.sepfield).remove();
			var contents = {};
			for (var lang in h.fields) {
				var f = h.fields[lang];
				contents[lang] = f.value;
				$(f).remove();
			}
			$(h.contentField).removeClass('qtranxs-translatable');
			delete contentHooks[h.contentField.id];
			return contents;
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 * @since 3.3
		 */
		this.removeContentHook = function (inpField) {
			if (!inpField || !inpField.id || !contentHooks[inpField.id])
				return false;
			var h = contentHooks[inpField.id];
			removeContentHookH(h);
			// @since 3.2.9.8 - h.contents -> h.fields
			$(inpField).removeClass('qtranxs-translatable');
			return true;
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 * Re-create a hook, after a piece of HTML is dynamically replaced with a custom Java script.
		 */
		this.refreshContentHook = function (inpField) {
			if (!inpField || !inpField.id)
				return false;
			var h = contentHooks[inpField.id];
			if (h)
				removeContentHookH(h);
			return qtx.addContentHook(inpField);
		};

		/**
		 * @since 3.4.6.9
		 */
		var getDisplayContentDefaultValue = function (contents) {
			if (contents[qTranslateConfig.language])
				return '(' + qTranslateConfig.language + ') ' + contents[qTranslateConfig.language];
			if (contents[qTranslateConfig.default_language])
				return '(' + qTranslateConfig.default_language + ') ' + contents[qTranslateConfig.default_language];
			for (var lang in contents) {
				if (!contents[lang])
					continue;
				return '(' + lang + ') ' + contents[lang];
			}
			return '';
		};

		/**
		 * @since 3.4.6.9
		 */
		var completeDisplayContent = function (contents) {
			var default_value = null;
			for (var lang in contents) {
				if (contents[lang])
					continue;
				if (!default_value)
					default_value = getDisplayContentDefaultValue(contents);
				contents[lang] = default_value;
			}
		};

		/**
		 * @since 3.2.7
		 */
		var displayHookNodes = [];
		var addDisplayHookNode = function (nd) {
			if (!nd.nodeValue)
				return 0;
			var blocks = qtranxj_get_split_blocks(nd.nodeValue);
			if (!blocks || !blocks.length || blocks.length === 1)
				return 0;
			var h = {};
			h.nd = nd;
			h.contents = qtranxj_split_blocks(blocks);
			completeDisplayContent(h.contents);
			nd.nodeValue = h.contents[qTranslateConfig.activeLanguage];
			displayHookNodes.push(h);
			return 1;
		};

		/**
		 * @since 3.2.7
		 */
		var displayHookAttrs = [];
		var addDisplayHookAttr = function (nd, attr) {
			if (!nd.hasAttribute(attr)) return 0;
			var value = nd.getAttribute(attr);
			var blocks = qtranxj_get_split_blocks(value);
			if (!blocks || !blocks.length || blocks.length === 1)
				return 0;
			var h = {};
			h.nd = nd;
			h.attr = attr;
			h.contents = qtranxj_split_blocks(blocks);
			completeDisplayContent(h.contents);
			nd.setAttribute(attr, h.contents[qTranslateConfig.activeLanguage]);
			displayHookAttrs.push(h);
			return 1;
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 * @since 3.2.7 switched to use of nodeValue instead of innerHTML.
		 */
		this.addDisplayHook = function (elem) {
			if (!elem || !elem.tagName)
				return 0;
			switch (elem.tagName) {
				case 'TEXTAREA':
					return 0;
				case 'INPUT':
					if (elem.type === 'submit' && elem.value) {
						return addDisplayHookAttr(elem, 'value');
					}
					return 0;
				default:
					break;
			}
			//co('addDisplayHook: elem: ',elem);
			var cnt = 0;
			if (elem.childNodes && elem.childNodes.length) {
				for (var i = 0; i < elem.childNodes.length; ++i) {
					var nd = elem.childNodes[i];
					switch (nd.nodeType) {
						// http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-1950641247
						case 1: // ELEMENT_NODE
							cnt += qtx.addDisplayHook(nd);
							break;
						case 2: // ATTRIBUTE_NODE
						case 3: // TEXT_NODE
							cnt += addDisplayHookNode(nd);
							break;
						default:
							break;
					}
				}
			}
			return cnt;
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 * @since 3.0
		 */
		this.addDisplayHookById = function (id) {
			return qtx.addDisplayHook(document.getElementById(id));
		};

		var updateTinyMCE = function (h) {
			text = h.contentField.value;
			if (h.wpautop && window.switchEditors) {
				text = window.switchEditors.wpautop(text);
			}
			h.mce.setContent(text, {format: 'html'});
		};

		var onTabSwitch = function (lang) {
			storeEditLanguage(lang);

			for (var i = displayHookNodes.length; --i >= 0;) {
				var h = displayHookNodes[i];
				if (h.nd.parentNode) {
					h.nd.nodeValue = h.contents[lang]; // IE gets upset here if node was removed
				} else {
					displayHookNodes.splice(i, 1); // node was removed by some other function
				}
			}
			for (var i = displayHookAttrs.length; --i >= 0;) {
				var h = displayHookAttrs[i];
				if (h.nd.parentNode) {
					h.nd.setAttribute(h.attr, h.contents[lang]);
				} else {
					displayHookAttrs.splice(i, 1); // node was removed by some other function
				}
			}
			if (qTranslateConfig.RAW)
				return;
			for (var key in contentHooks) {
				var h = contentHooks[key];
				var mce = h.mce && !h.mce.hidden;
				if (mce) {
					h.mce.save({format: 'html'});
				}

				var text = h.contentField.value.trim();
				var blocks = qtranxj_get_split_blocks(text);
				if (!blocks || blocks.length <= 1) {
					// value is not ML, switch it to other language
					h.fields[h.lang].value = text;
					h.lang = lang;
					var value = h.fields[h.lang].value;
					if (h.contentField.placeholder && value !== '') {
						// since 3.2.7
						h.contentField.placeholder = '';
					}
					h.contentField.value = value;
					if (mce) {
						updateTinyMCE(h);
					}
				} else {
					// value is ML, fill out values per language
					var contents = qtranxj_split_blocks(blocks);
					for (var lng in h.fields) {
						h.fields[lng].value = contents[lng];
					}
					h.lang = lang;
				}
			}
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 * @since 3.0
		 */
		this.addDisplayHooks = function (elems) {
			for (var i = 0; i < elems.length; ++i) {
				var e = elems[i];
				qtx.addDisplayHook(e);
			}
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 * @since 3.4.7
		 */
		this.addDisplayHookAttrs = function (elem, attrs) {
			for (var j = 0; j < attrs.length; ++j) {
				var a = attrs[j];
				addDisplayHookAttr(elem, a);
			}
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 * @since 3.4.7
		 */
		this.addDisplayHooksAttrs = function (elems, attrs) {
			for (var i = 0; i < elems.length; ++i) {
				var e = elems[i];
				qtx.addDisplayHookAttrs(e, attrs);
			}
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 * @since 3.3
		 */
		this.addDisplayHooksByClass = function (nm, container) {
			var elems = container.getElementsByClassName(nm);
			qtx.addDisplayHooks(elems);
		};

		/**
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 * @since 3.3
		 */
		this.addDisplayHooksByTagInClass = function (nm, tag, container) {
			var elems = container.getElementsByClassName(nm);
			for (var i = 0; i < elems.length; ++i) {
				var elem = elems[i];
				var items = elem.getElementsByTagName(tag);
				qtx.addDisplayHooks(items);
			}
		};


		/**
		 * adds custom hooks from configuration
		 * @since 3.1-b2 - renamed to addCustomContentHooks, since addContentHooks used in qTranslateConfig.js
		 * @since 3.0 - addContentHooks
		 */
		this.addCustomContentHooks = function () {
			for (var i = 0; i < qTranslateConfig.custom_fields.length; ++i) {
				var fieldName = qTranslateConfig.custom_fields[i];
				qtx.addContentHookByIdName(fieldName);
			}
			for (var i = 0; i < qTranslateConfig.custom_field_classes.length; ++i) {
				var className = qTranslateConfig.custom_field_classes[i];
				qtx.addContentHooksByClass(className);
			}
			if (qTranslateConfig.LSB)
				setTinyMceInit();
		};

		/**
		 * adds translatable hooks for fields marked with classes
		 * i18n-multilingual
		 * i18n-multilingual-curly
		 * i18n-multilingual-term
		 * i18n-multilingual-slug
		 * i18n-multilingual-display
		 * @since 3.4
		 */
		var addMultilingualHooks = function () {
			$('.i18n-multilingual').each(function (i, e) {
				qtx.addContentHook(e, '[');
			});
			$('.i18n-multilingual-curly').each(function (i, e) {
				qtx.addContentHook(e, '{');
			});
			$('.i18n-multilingual-term').each(function (i, e) {
				qtx.addContentHook(e, 'term');
			});
			$('.i18n-multilingual-slug').each(function (i, e) {
				qtx.addContentHook(e, 'slug');
			});
			$('.i18n-multilingual-display').each(function (i, e) {
				qtx.addDisplayHook(e);
			});
		};

		/**
		 * Parses page configuration, loaded in qtranxf_get_admin_page_config_post_type.
		 * @since 3.1-b2
		 */
		var addPageHooks = function (page_config_forms) {
			for (var form_id in page_config_forms) {
				var frm = page_config_forms[form_id];
				var form;
				if (frm.form) {
					if (frm.form.id) {
						form = document.getElementById(frm.form.id);
					} else if (frm.form.jquery) {
						form = $(frm.form.jquery);
					} else if (frm.form.name) {
						var elms = document.getElementsByName(frm.form.name);
						if (elms && elms.length) {
							form = elms[0];
						}
					}
				} else {
					form = document.getElementById(form_id);
				}
				if (!form) {
					form = getWrapForm();
					if (!form)
						form = document;
				}
				for (var handle in frm.fields) {
					var fld = frm.fields[handle];
					var containers = [];
					if (fld.container_id) {
						var container = document.getElementById(fld.container_id);
						if (container)
							containers.push(container);
					} else if (fld.container_jquery) {
						containers = $(fld.container_jquery);
					} else if (fld.container_class) {
						containers = document.getElementsByClassName(fld.container_class);
					} else {// if(form){
						containers.push(form);
					}
					var sep = fld.encode;
					switch (sep) {
						case 'none':
							continue;
						case 'display':
							if (fld.jquery) {
								for (var i = 0; i < containers.length; ++i) {
									var container = containers[i];
									var fields = $(container).find(fld.jquery);
									if (fld.attrs) {
										qtx.addDisplayHooksAttrs(fields, fld.attrs);
									} else {
										qtx.addDisplayHooks(fields);
									}
								}
							} else {
								var id = fld.id ? fld.id : handle;
								//co('addPageHooks:display: id=',id);
								var field = document.getElementById(id);
								if (fld.attrs) {
									qtx.addDisplayHookAttrs(field, fld.attrs);
								} else {
									qtx.addDisplayHook(field);
								}
							}
							break;
						case '[': // b - bracket
						case '<': // c - comment
						case '{': // s - swirly/curly bracket
						case 'byline':
						default:
							if (fld.jquery) {
								for (var i = 0; i < containers.length; ++i) {
									var container = containers[i];
									var fields = $(container).find(fld.jquery);
									qtx.addContentHooks(fields, sep, fld.name);
								}
							} else {
								var id = fld.id ? fld.id : handle;
								qtx.addContentHookById(id, sep, fld.name);
							}
							break;
					}
				}
			}
		};

		this.addContentHooksTinyMCE = function () {
			function setEditorHooks(ed) {
				var id = ed.id;
				if (!id)
					return;
				var h = contentHooks[id];
				if (!h)
					return;
				if (h.mce) {
					return;  // already initialized
				}
				h.mce = ed;

				/**
				 * Highlighting the translatable fields
				 * @since 3.2-b3
				 */
				ed.getContainer().className += ' qtranxs-translatable';
				ed.getElement().className += ' qtranxs-translatable';

				var updateTinyMCEonInit = h.updateTinyMCEonInit;
				if (updateTinyMCEonInit == null) {
					// 'tmce-active' or 'html-active' was not provided on the wrapper
					var text_e = ed.getContent({format: 'html'}).replace(/\s+/g, '');
					var text_h = h.contentField.value.replace(/\s+/g, '');
					/**
					 * @since 3.2.9.8 - this is an ugly trick.
					 * Before this version, it was working relying on properly timed synchronisation of the page loading process,
					 * which did not work correctly in some browsers like IE or MAC OS, for example.
					 * Now, function setTinyMceInit is called after HTML loaded, before TinyMCE initialization, and it always set
					 * tinyMCEPreInit.mceInit, which causes to call this function, setEditorHooks, on TinyMCE initialization of each editor.
					 * However, function setEditorHooks gets invoked in two ways:
					 *
					 * 1. On page load, when Visual mode is initially on.
					 *      In this case we need to apply updateTinyMCE, which possibly applies wpautop.
					 *      Without q-X, WP applies wpautop in this case in php code in /wp-includes/class-wp-editor.php,
					 *      function 'editor', line "add_filter('the_editor_content', 'wp_richedit_pre');".
					 *      q-X disables this call in 'function qtranxf_the_editor',
					 *      since wpautop does not work correctly on multilingual values, and there is no filter to adjust its behaviour.
					 *      So, here we have to apply back wpautop to single-language value, which is achieved
					 *      with a call to updateTinyMCE(h) below.
					 *
					 * 2. When user switches to Visual mode for the first time from a page, which was initially loaded in Text mode.
					 *      In this case, wpautop gets applied internally inside TinyMCE, and we do not need to call updateTinyMCE(h) below.
					 *
					 * We could not figure out a good way to distinct within this function which way it was called,
					 * except this tricky comparison on the next line.
					 *
					 * If somebody finds out a better way, please let us know at qtranslateteam@gmail.com.
					 */
					updateTinyMCEonInit = text_e !== text_h;
				}
				if (updateTinyMCEonInit) {
					updateTinyMCE(h);
				}
				return h;
			}

			/** Sets hooks on HTML-loaded TinyMCE editors via tinyMCEPreInit.mceInit. */
			setTinyMceInit = function () {
				if (!window.tinyMCEPreInit || !window.tinyMCE) {
					return;
				}
				for (var key in contentHooks) {
					var h = contentHooks[key];
					if (h.contentField.tagName !== 'TEXTAREA' || h.mce || h.mceInit || !tinyMCEPreInit.mceInit[key])
						continue;
					h.mceInit = tinyMCEPreInit.mceInit[key];
					if (h.mceInit.wpautop) {
						h.wpautop = h.mceInit.wpautop;
						var wrappers = tinymce.DOM.select('#wp-' + key + '-wrap');
						if (wrappers && wrappers.length) {
							h.wrapper = wrappers[0];
							if (h.wrapper) {
								if (tinymce.DOM.hasClass(h.wrapper, 'tmce-active'))
									h.updateTinyMCEonInit = true;
								if (tinymce.DOM.hasClass(h.wrapper, 'html-active'))
									h.updateTinyMCEonInit = false;
								// otherwise h.updateTinyMCEonInit stays undetermined
							}
						}
					} else {
						h.updateTinyMCEonInit = false;
					}
					tinyMCEPreInit.mceInit[key].init_instance_callback = function (ed) {
						setEditorHooks(ed);
					}
				}
			};
			setTinyMceInit();

			/** Adds more TinyMCE editors, which may have been initialized dynamically. */
			loadTinyMceHooks = function () {
				if (!window.tinyMCE)
					return;
				if (!tinyMCE.editors)
					return;
				for (var i = 0; i < tinyMCE.editors.length; ++i) {
					var ed = tinyMCE.editors[i];
					setEditorHooks(ed);
				}
			};
			window.addEventListener('load', loadTinyMceHooks);
		};

		if (!qTranslateConfig.onTabSwitchFunctions)
			qTranslateConfig.onTabSwitchFunctions = [];
		if (!qTranslateConfig.onTabSwitchFunctionsSave)
			qTranslateConfig.onTabSwitchFunctionsSave = [];
		if (!qTranslateConfig.onTabSwitchFunctionsLoad)
			qTranslateConfig.onTabSwitchFunctionsLoad = [];

		this.addLanguageSwitchListener = function (func) {
			qTranslateConfig.onTabSwitchFunctions.push(func);
		};

		/**
		 * @since 3.2.9.8.6
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 * The function passed will be called when user presses one of the Language Switching Buttons
		 * before the content of all fields hooked is replaced with an appropriate language.
		 * Two arguments are supplied:
		 * - two-letter language code of currently active language from which the edit language is being switched.
		 * - the language code to which the edit language is being switched.
		 * The value of "this" is set to the only global instance of qTranslateX object.
		 */
		this.addLanguageSwitchBeforeListener = function (func) {
			qTranslateConfig.onTabSwitchFunctionsSave.push(func);
		};

		/**
		 * @since 3.3.2
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 * Delete handler previously added by function addLanguageSwitchBeforeListener.
		 */
		this.delLanguageSwitchBeforeListener = function (func) {
			for (var i = 0; i < qTranslateConfig.onTabSwitchFunctionsSave.length; ++i) {
				var f = qTranslateConfig.onTabSwitchFunctionsSave[i];
				if (f !== func)
					continue;
				qTranslateConfig.onTabSwitchFunctionsSave.splice(i, 1);
				return;
			}
		};

		/**
		 * @since 3.2.9.8.6
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 * The function passed will be called when user presses one of the Language Switching Buttons
		 * after the content of all fields hooked is replaced with an appropriate language.
		 * Two arguments are supplied:
		 * - two-letter language code of active language to which the edit language is already switched.
		 * - the language code from which the edit language is being switched.
		 * The value of "this" is set to the only global instance of qTranslateX object.
		 */
		this.addLanguageSwitchAfterListener = function (func) {
			qTranslateConfig.onTabSwitchFunctionsLoad.push(func);
		};

		/**
		 * @since 3.3.2
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 * Delete handler previously added by function addLanguageSwitchAfterListener.
		 */
		this.delLanguageSwitchAfterListener = function (func) {
			for (var i = 0; i < qTranslateConfig.onTabSwitchFunctionsLoad.length; ++i) {
				var f = qTranslateConfig.onTabSwitchFunctionsLoad[i];
				if (f !== func)
					continue;
				qTranslateConfig.onTabSwitchFunctionsLoad.splice(i, 1);
				return;
			}
		};

		/**
		 * @since 3.2.9.8.9
		 * Designed as interface for other plugin integration. The documentation is available at
		 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
		 *
		 */
		this.enableLanguageSwitchingButtons = function (on) {
			var display = on ? 'block' : 'none';
			for (var lang in qTranslateConfig.tabSwitches) {
				var tabSwitches = qTranslateConfig.tabSwitches[lang];
				for (var i = 0; i < tabSwitches.length; ++i) {
					var tabSwitchParent = tabSwitches[i].parentElement;
					tabSwitchParent.style.display = display;
					break;
				}
				break;
			}
		};

		var getWrapForm = function () {
			var wraps = document.getElementsByClassName('wrap');
			for (var i = 0; i < wraps.length; ++i) {
				var w = wraps[i];
				var forms = w.getElementsByTagName('form');
				if (forms.length)
					return forms[0];
			}
			var forms = document.getElementsByTagName('form');
			if (forms.length === 1)
				return forms[0];
			for (var i = 0; i < forms.length; ++i) {
				var f = forms[i];
				wraps = f.getElementsByClassName('wrap');
				if (wraps.length)
					return f;
			}
			return null;
		};

		var getFormWrap = function () {
			var forms = document.getElementsByTagName('form');
			for (var i = 0; i < forms.length; ++i) {
				var f = forms[i];
				var wraps = f.getElementsByClassName('wrap');
				if (wraps.length)
					return wraps[0];
			}
			var wraps = document.getElementsByClassName('wrap');
			for (var i = 0; i < wraps.length; ++i) {
				var w = wraps[i];
				forms = w.getElementsByTagName('form');
				if (forms.length)
					return w;
			}
			return null;
		};

		if (typeof(pg.addContentHooks) == "function")
			pg.addContentHooks(this);

		if (qTranslateConfig.page_config && qTranslateConfig.page_config.forms)
			addPageHooks(qTranslateConfig.page_config.forms);

		addMultilingualHooks();

		if (!displayHookNodes.length && !displayHookAttrs.length) {
			var ok = false;
			for (var key in contentHooks) {
				ok = true;
				break;
			}
			if (!ok) {
				return;
			}
		}

		this.onLoadLanguage = function (lang, langFrom) {
			var onTabSwitchFunctionsLoad = qTranslateConfig.onTabSwitchFunctionsLoad;
			for (var i = 0; i < onTabSwitchFunctionsLoad.length; ++i) {
				onTabSwitchFunctionsLoad[i].call(qTranslateConfig.qtx, lang, langFrom);
			}
		};

		/**
		 * former switchTab
		 * @since 3.3.2
		 */
		this.switchActiveLanguage = function (lang) {
			if (qTranslateConfig.activeLanguage === lang) {
				return;
			}
			if (qTranslateConfig.activeLanguage) {
				var ok2switch = true;
				var onTabSwitchFunctionsSave = qTranslateConfig.onTabSwitchFunctionsSave;
				for (var i = 0; i < onTabSwitchFunctionsSave.length; ++i) {
					var ok = onTabSwitchFunctionsSave[i].call(qTranslateConfig.qtx, qTranslateConfig.activeLanguage, lang);
					if (ok === false)
						ok2switch = false;
				}
				if (!ok2switch)
					return; // cancel button switch, if one of onTabSwitchFunctionsSave returned 'false'

				var tabSwitches = qTranslateConfig.tabSwitches[qTranslateConfig.activeLanguage];
				for (var i = 0; i < tabSwitches.length; ++i) {
					tabSwitches[i].classList.remove(qTranslateConfig.lsb_style_active_class);
					$(tabSwitches[i]).find('.button').removeClass('active');
				}
			}

			var langFrom = qTranslateConfig.activeLanguage;
			qTranslateConfig.activeLanguage = lang;
			$('input[name="qtranslate-edit-language"]').val(lang);

			{
				var tabSwitches = qTranslateConfig.tabSwitches[qTranslateConfig.activeLanguage];
				for (var i = 0; i < tabSwitches.length; ++i) {
					tabSwitches[i].classList.add(qTranslateConfig.lsb_style_active_class);
					$(tabSwitches[i]).find('.button').addClass('active');
				}
			}
			var onTabSwitchFunctions = qTranslateConfig.onTabSwitchFunctions;
			for (var i = 0; i < onTabSwitchFunctions.length; ++i) {
				onTabSwitchFunctions[i].call(qTranslateConfig.qtx, lang, langFrom);
			}
			qtx.onLoadLanguage(lang, langFrom);
		};

		this.clickSwitchLanguage = function () {
			var tabSwitch = $(this).hasClass('button') ? this.parentNode : this;
			var lang = tabSwitch.lang;
			if (!lang) {
				alert('qTranslate-XT: This should not have happened: Please, report this incident to the developers: !lang');
				return;
			}
			if ($('.qtranxs-lang-switch-wrap').hasClass('copying')) {
				qtx.copyContentFrom(lang);
				$(tabSwitch).find('.button').blur();	// remove focus of source language in case of layout with button
				$('.qtranxs-lang-switch-wrap').removeClass('copying');
				$('.qtranxs-lang-copy .button').removeClass('active');
			} else {
				qtx.switchActiveLanguage(lang);
			}
		};

		this.toggleCopyFrom = function () {
			$('.qtranxs-lang-switch-wrap').toggleClass('copying');
			$('.qtranxs-lang-copy .button').toggleClass('active');
			// store or restore original title according to current mode (copy or switch)
			if ($('.qtranxs-lang-switch-wrap').hasClass('copying')) {
				$('.qtranxs-lang-switch').each(function () {
					$(this).attr('orig-title', $(this).attr('title'));
					if ($(this).attr('lang') === qTranslateConfig.activeLanguage)
						$(this).attr('title', qTranslateConfig.strings.CopyFromAlt);
					else
						$(this).attr('title', qTranslateConfig.strings.CopyFrom + ' [:' + $(this).attr('lang') + ']');
				});
			}
			else {
				$('.qtranxs-lang-switch').each(function () {
					$(this).attr('title', $(this).attr('orig-title'));
				});
			}
		};

		this.copyContentFrom = function (langFrom) {
			var lang = qTranslateConfig.activeLanguage;
			var changed = false;
			for (var key in contentHooks) {
				var h = contentHooks[key];
				var mce = h.mce && !h.mce.hidden;
				var value = mce ? h.mce.getContent({format: 'html'}) : h.contentField.value;
				if (value)
					continue; // do not overwrite existent content
				value = h.fields[langFrom].value;
				if (!value)
					continue;
				h.contentField.value = value;
				if (mce)
					updateTinyMCE(h);
				changed = true;
			}
			if (changed)
				qtx.onLoadLanguage(lang, langFrom);
		};

		/**
		 * @since 3.3.2
		 */
		this.createSetOfLSBwith = function (lsb_style_extra_wrap_classes) {
			var langSwitchWrap = qtranxj_ce('ul', {className: 'qtranxs-lang-switch-wrap ' + lsb_style_extra_wrap_classes});
			var langs = qTranslateConfig.language_config;
			if (!qTranslateConfig.tabSwitches)
				qTranslateConfig.tabSwitches = {};
			for (var lang in langs) {
				var lang_conf = langs[lang];
				var flag_location = qTranslateConfig.flag_location;
				var li_title = qTranslateConfig.strings.ShowIn + lang_conf.admin_name + ' [:' + lang + ']';
				var tabSwitch = qtranxj_ce('li', {
					lang: lang,
					className: 'qtranxs-lang-switch qtranxs-lang-switch-' + lang,
					title: li_title,
					onclick: qtx.clickSwitchLanguage
				}, langSwitchWrap);
				var tabItem = tabSwitch;
				if (qTranslateConfig.lsb_style_subitem === 'button') {
					// reuse WordPress secondary button
					tabItem = qtranxj_ce('button', {className: 'button button-secondary', type: 'button'}, tabSwitch);
				}
				qtranxj_ce('img', {src: flag_location + lang_conf.flag}, tabItem);
				qtranxj_ce('span', {innerHTML: lang_conf.name}, tabItem);
				if (qTranslateConfig.activeLanguage === lang) {
					tabSwitch.classList.add(qTranslateConfig.lsb_style_active_class);
					$(tabSwitch).find('.button').addClass('active');
				}
				if (!qTranslateConfig.tabSwitches[lang])
					qTranslateConfig.tabSwitches[lang] = [];
				qTranslateConfig.tabSwitches[lang].push(tabSwitch);
			}
			if (!qTranslateConfig.hide_lsb_copy_content) {
				var tab = qtranxj_ce('li', {className: 'qtranxs-lang-copy'}, langSwitchWrap);
				var btn = qtranxj_ce('button', {
					className: 'button button-secondary',
					type: 'button',
					title: qTranslateConfig.strings.CopyFromAlt,
					onclick: qtx.toggleCopyFrom
				}, tab);
				qtranxj_ce('span', {innerHTML: qTranslateConfig.strings.CopyFrom}, btn);
			}
			return langSwitchWrap;
		};

		/**
		 * @since 3.4.8
		 */
		this.createSetOfLSB = function () {
			return qtx.createSetOfLSBwith(qTranslateConfig.lsb_style_wrap_class + ' widefat');
		};

		var setupMetaBoxLSB = function () {
			var mb = document.getElementById('qtranxs-meta-box-lsb');
			if (!mb)
				return;

			var inside_elems = mb.getElementsByClassName('inside');
			if (!inside_elems.length)
				return; // consistency check in case WP did some changes

			mb.className += ' closed';
			$(mb).find('.hndle').remove(); // original h3 element is replaced with span below

			var sp = document.createElement('span');
			mb.insertBefore(sp, inside_elems[0]);
			sp.className = 'hndle ui-sortable-handle';

			var langSwitchWrap = qtx.createSetOfLSBwith(qTranslateConfig.lsb_style_wrap_class);
			sp.appendChild(langSwitchWrap);
			$('#qtranxs-meta-box-lsb .hndle').unbind('click.postboxes');
		};

		if (qTranslateConfig.LSB) {
			// additional initialization
			this.addContentHooksTinyMCE();
			setupMetaBoxLSB();

			// create sets of LSB
			var anchors = [];
			if (qTranslateConfig.page_config && qTranslateConfig.page_config.anchors) {
				for (var id in qTranslateConfig.page_config.anchors) {
					var anchor = qTranslateConfig.page_config.anchors[id];
					var f = document.getElementById(id);
					if (f) {
						anchors.push({f: f, where: anchor.where});
					} else if (anchor.jquery) {
						var list = $(anchor.jquery);
						for (var i = 0; i < list.length; ++i) {
							var f = list[i];
							anchors.push({f: f, where: anchor.where});
						}
					}
				}
			}
			if (!anchors.length) {
				var f = pg.langSwitchWrapAnchor;
				if (!f) {
					f = getWrapForm();
				}
				if (f) anchors.push({f: f, where: 'before'});
			}
			for (var i = 0; i < anchors.length; ++i) {
				var anchor = anchors[i];
				if (!anchor.where || anchor.where.indexOf('before') >= 0) {
					var langSwitchWrap = qtx.createSetOfLSB();
					anchor.f.parentNode.insertBefore(langSwitchWrap, anchor.f);
				}
				if (anchor.where && anchor.where.indexOf('after') >= 0) {
					var langSwitchWrap = qtx.createSetOfLSB();
					anchor.f.parentNode.insertBefore(langSwitchWrap, anchor.f.nextSibling);
				}
				if (anchor.where && anchor.where.indexOf('first') >= 0) {
					var langSwitchWrap = qtx.createSetOfLSB();
					anchor.f.insertBefore(langSwitchWrap, anchor.f.firstChild);
				}
				if (anchor.where && anchor.where.indexOf('last') >= 0) {
					var langSwitchWrap = qtx.createSetOfLSB();
					anchor.f.insertBefore(langSwitchWrap, null);
				}
			}

			/**
			 * @since 3.2.4 Synchronization of multiple sets of Language Switching Buttons
			 */
			this.addLanguageSwitchListener(onTabSwitch);
			if (pg.onTabSwitch) {
				this.addLanguageSwitchListener(pg.onTabSwitch);
			}
		}
	};

	/**
	 * Designed as interface for other plugin integration. The documentation is available at
	 * https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
	 *
	 * qTranslateX instance is saved in global variable qTranslateConfig.qtx,
	 * which can be used by theme or plugins to dynamically change content hooks.
	 *
	 * Note: be sure to enqueue this script before using it in other plugin (!)
	 *
	 * @since 3.4
	 */
	qTranslateConfig.js.get_qtx = function () {
		if (!qTranslateConfig.qtx)
			qTranslateConfig.qtx = new qTranslateX(qTranslateConfig.js);
		return qTranslateConfig.qtx;
	};

	$(function() {
		qTranslateConfig.js.get_qtx();
	});
})(jQuery);
;