[website] Support Portal  (#14144)

* [init] support portal

* [support-portal] Get started sections and forum activity

* [support-portal] integrate API search for forums, docs, etc

* [support-portal] integrate doctype docs search via global search

* [support-portal] /help page UI
diff --git a/erpnext/templates/pages/help.html b/erpnext/templates/pages/help.html
new file mode 100644
index 0000000..f7568d5
--- /dev/null
+++ b/erpnext/templates/pages/help.html
@@ -0,0 +1,62 @@
+{% extends "templates/web.html" %}
+
+{% block title %} {{ _("Help") }} {% endblock %}
+
+{% block header %}<h1>{{ _("Help") }}</h1>
+
+<div style="margin-bottom: 20px;">
+	<form action='/search_help'>
+	<input name='q' class='form-control' type='text'
+		style='max-width: 400px; display: inline-block; margin-right: 10px;'
+		value='{{ frappe.form_dict.q or ''}}'
+		{% if not frappe.form_dict.q%}placeholder="{{ _("What do you need help with?") }}"{% endif %}>
+	<input type='submit'
+		class='btn btn-sm btn-primary btn-search' value="{{ _("Search") }}">
+	</form>
+</div>
+
+{% for section in get_started_sections %}
+<div style="margin-bottom: 30px;">
+	<h2>{{ section["name"] }}</h2>
+	{% for item in section["items"] %}
+	<div style="margin: 20px 0;">
+		<a href="{{ item.link }}"><b>{{ item.title }}</b></a>
+		{% if item.description -%}
+		<p>{{ item.description }}</p>
+		{%- endif %}
+	</div>
+	{% endfor %}
+	<p><a href="/kb/support">{{ _("See All Articles") }}</a></p>
+	<hr>
+</div>
+{% endfor %}
+
+<div style="margin-bottom: 30px;">
+	<h2>{{ _("Forum Activity") }}</h2>
+	{% for topic in topics %}
+	<div style="margin: 20px 0;">
+		<a href="{{ topic[post_params.link] }}">
+			<b>{{ topic[post_params.title] }}</b>
+		</a>
+		{% if topic[post_params.description] -%}
+		<p>{{ topic[post_params.description] }}</p>
+		{%- endif %}
+	</div>
+	{% endfor %}
+	<p><a href="{{ forum_url }}">{{ _("Visit the forums") }}</a></p>
+	<hr>
+</div>
+
+<div style="margin-bottom: 20px;">
+	<h2>{{ _("Your tickets") }}</h2>
+		{% for doc in issues %}
+			{% include "templates/includes/issue_row.html" %}
+		{% endfor %}
+	<p><a href="/issues">{{ _("See all open tickets") }}</a></p>
+</div>
+
+<a href="/issues?new=1" class="btn btn-primary btn-new btn-sm">
+	{{ _("Open a new ticket") }}
+</a>
+
+{% endblock %}
diff --git a/erpnext/templates/pages/help.py b/erpnext/templates/pages/help.py
new file mode 100644
index 0000000..754a09c
--- /dev/null
+++ b/erpnext/templates/pages/help.py
@@ -0,0 +1,41 @@
+from __future__ import unicode_literals
+import frappe, json
+
+import requests
+
+def get_context(context):
+	context.no_cache = 1
+	settings = frappe.get_doc("Support Settings", "Support Settings")
+	s = settings
+
+	# Get Started sections
+	sections = json.loads(s.get_started_sections)
+	context.get_started_sections = sections
+
+	# Forum posts
+	topics_data, post_params = get_forum_posts(s)
+	context.post_params = post_params
+	context.forum_url = s.forum_url
+	context.topics = topics_data[:3]
+
+	# Issues
+	context.issues = frappe.get_list("Issue")[:3]
+
+def get_forum_posts(s):
+	response = requests.get(s.forum_url + '/' + s.get_latest_query)
+	response.raise_for_status()
+	response_json = response.json()
+
+	topics_data = {} # it will actually be an array
+	key_list = s.response_key_list.split(',')
+	for key in key_list:
+		topics_data = response_json.get(key) if not topics_data else topics_data.get(key)
+
+	for topic in topics_data:
+		topic["link"] = s.forum_url + '/' + s.post_route_string + '/' + str(topic.get(s.post_route_key))
+
+	post_params = {
+		"title": s.post_title_key,
+		"description": s.post_description_key
+	}
+	return topics_data, post_params
diff --git a/erpnext/templates/pages/search_help.html b/erpnext/templates/pages/search_help.html
new file mode 100644
index 0000000..bf19540
--- /dev/null
+++ b/erpnext/templates/pages/search_help.html
@@ -0,0 +1,7 @@
+{% extends "templates/web.html" %}
+
+{% block page_content %}
+
+{% include "templates/includes/search_template.html" %}
+
+{% endblock %}
diff --git a/erpnext/templates/pages/search_help.py b/erpnext/templates/pages/search_help.py
new file mode 100644
index 0000000..e564e21
--- /dev/null
+++ b/erpnext/templates/pages/search_help.py
@@ -0,0 +1,99 @@
+from __future__ import unicode_literals
+import frappe, requests
+from frappe import _
+from jinja2 import utils
+from html2text import html2text
+from frappe.utils import sanitize_html
+from frappe.utils.global_search import search
+
+def get_context(context):
+	context.no_cache = 1
+	if frappe.form_dict.q:
+		query = str(utils.escape(sanitize_html(frappe.form_dict.q)))
+		context.title = _('Help Results for "{0}"').format(query)
+		context.route = '/search_help'
+		d = frappe._dict()
+		d.results_sections = get_help_results_sections(query)
+		context.update(d)
+	else:
+		context.title = _('Docs Search')
+
+@frappe.whitelist(allow_guest = True)
+def get_help_results_sections(text):
+	out = []
+	settings = frappe.get_doc("Support Settings", "Support Settings")
+
+	for api in settings.search_apis:
+		results = []
+		if api.source_type == "API":
+			response_json = get_response(api, text)
+			topics_data = get_topics_data(api, response_json)
+			results = prepare_api_results(api, topics_data)
+		else:
+			# Source type is Doctype
+			doctype = api.source_doctype
+			raw = search(text, 0, 20, doctype)
+			results = prepare_doctype_results(api, raw)
+
+		if results:
+			# Add section
+			out.append({
+				"title": api.source_name,
+				"results": results
+			})
+
+	return out
+
+def get_response(api, text):
+	response = requests.get(api.base_url + '/' + api.query_route, data={
+		api.search_term_param_name: text
+	})
+
+	response.raise_for_status()
+	return response.json()
+
+def get_topics_data(api, response_json):
+	if not response_json:
+		response_json = {}
+	topics_data = {} # it will actually be an array
+	key_list = api.response_result_key_path.split(',')
+
+	for key in key_list:
+		topics_data = response_json.get(key) if not topics_data else topics_data.get(key)
+
+	return topics_data or []
+
+def prepare_api_results(api, topics_data):
+	if not topics_data:
+		topics_data = []
+
+	results = []
+	for topic in topics_data:
+		route = api.base_url + '/' + (api.post_route  + '/' if api.post_route else "")
+		for key in api.post_route_key_list.split(','):
+			route += unicode(topic[key])
+
+		results.append(frappe._dict({
+			'title': topic[api.post_title_key],
+			'preview': html2text(topic[api.post_description_key]),
+			'route': route
+		}))
+	return results[:5]
+
+def prepare_doctype_results(api, raw):
+	results = []
+	for r in raw:
+		prepared_result = {}
+		parts = r["content"].split(' ||| ')
+
+		for part in parts:
+			pair = part.split(' : ', 1)
+			prepared_result[pair[0]] = pair[1]
+
+		results.append(frappe._dict({
+			'title': prepared_result[api.result_title_field],
+			'preview': prepared_result[api.result_preview_field],
+			'route': prepared_result[api.result_route_field]
+		}))
+
+	return results