<?php namespace HashOver;// Copyright (C) 2015-2021 Jacob Barkdull// This file is part of HashOver.//// HashOver is free software: you can redistribute it and/or modify// it under the terms of the GNU Affero General Public License as// published by the Free Software Foundation, either version 3 of the// License, or (at your option) any later version.//// HashOver 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 Affero General Public License for more details.//// You should have received a copy of the GNU Affero General Public License// along with HashOver.  If not, see <http://www.gnu.org/licenses/>.class FormUI{	protected $mode;	protected $setup;	protected $commentCounts;	protected $locale;	protected $avatars;	protected $misc;	protected $login;	protected $cookies;	protected $pageTitle;	protected $pageURL;	protected $emphasizedField;	protected $defaultLoginInputs;	public $postComment;	public $popularComments;	public $comments;	public function __construct ($mode = 'javascript', Setup $setup, array $counts)	{		// Store parameters as properties		$this->mode = $mode;		$this->setup = $setup;		$this->commentCounts = $counts;		// Instantiate various classes		$this->locale = new Locale ($setup);		$this->login = new Login ($setup);		$this->cookies = new Cookies ($setup, $this->login);		$this->avatars = new Avatars ($setup);		$this->pageTitle = $this->setup->pageTitle;		$this->pageURL = $this->setup->pageURL;		// Attempt to get form field submission failed on		$failedField = $this->cookies->getValue ('failed-on');		// Set the field to emphasize after a failed post		if ($failedField !== '') {			$this->emphasizedField = $failedField;		}		// "Post a comment" locale strings		$this->postComment = $this->locale->text['post-a-comment'];		// Use "Post a comment on <page title>" locale instead if configured to		if ($this->setup->displaysTitle !== false and !empty ($this->pageTitle)) {			$this->postComment = sprintf (				$this->locale->text['post-a-comment-on'],				$this->pageTitle			);		}		// Create default login inputs elements		$this->defaultLoginInputs = $this->loginInputs ();	}	// Returns pseudo-namespace prefixed string	public function prefix ($id = '', $template = true)	{		// Return template prefix in JavaScript mode		if ($template === true and $this->mode !== 'php') {			return '{hashover}-' . $id;		}		// Initial prefix		$prefix = 'hashover';		// Return simple prefix if we're on first instance		if ($this->setup->instanceNumber > 1) {			$prefix .= '-' . $this->setup->instanceNumber;		}		// Return prefixed ID if one is given		if (!empty ($id)) {			return $prefix . '-' . $id;		}		// Otherwise, return prefix by itself		return $prefix;	}	// Creates input elements for user login information	protected function loginInputs ($permalink = '', $edit_form = false, $name = '', $email = '', $website = '')	{		// Reply/edit form indicator		$is_form = !empty ($permalink);		// Prepend dash to permalink if present		$permalink = $is_form ? '-' . $permalink : '';		// Login input attribute information		$login_input_attributes = array (			'name' => array (				'wrapper-class'		=> 'hashover-name-input',				'label-class'		=> 'hashover-name-label',				'placeholder'		=> $this->locale->text['name'],				'input-id'		=> $this->prefix ('main-name' . $permalink, $is_form),				'input-type'		=> 'text',				'input-name'		=> 'name',				'input-title'		=> $this->locale->text['name-tip'],				'input-value'		=> Misc::makeXSSsafe ($this->login->name),				'autocomplete'		=> 'username'			),			'password' => array (				'wrapper-class'		=> 'hashover-password-input',				'label-class'		=> 'hashover-password-label',				'placeholder'		=> $this->locale->text['password'],				'input-id'		=> $this->prefix ('main-password' . $permalink, $is_form),				'input-type'		=> 'password',				'input-name'		=> 'password',				'input-title'		=> $this->locale->text['password-tip'],				'input-value'		=> '',				'autocomplete'		=> 'current-password'			),			'email' => array (				'wrapper-class'		=> 'hashover-email-input',				'label-class'		=> 'hashover-email-label',				'placeholder'		=> $this->locale->text['email'],				'input-id'		=> $this->prefix ('main-email' . $permalink, $is_form),				'input-type'		=> 'email',				'input-name'		=> 'email',				'input-title'		=> $this->locale->text['email-tip'],				'input-value'		=> Misc::makeXSSsafe ($this->login->email),				'autocomplete'		=> 'off'			),			'website' => array (				'wrapper-class'		=> 'hashover-website-input',				'label-class'		=> 'hashover-website-label',				'placeholder'		=> $this->locale->text['website'],				'input-id'		=> $this->prefix ('main-website' . $permalink, $is_form),				'input-type'		=> 'url',				'input-name'		=> 'website',				'input-title'		=> $this->locale->text['website-tip'],				'input-value'		=> Misc::makeXSSsafe ($this->login->website),				'autocomplete'		=> 'off'			)		);		// Change input values to specified values		if ($edit_form === true) {			$login_input_attributes['name']['input-value'] = $name;			$login_input_attributes['password']['placeholder'] = $this->locale->text['confirm-password'];			$login_input_attributes['password']['input-title'] = $this->locale->text['confirm-password'];			$login_input_attributes['email']['input-value'] = $email;			$login_input_attributes['website']['input-value'] = $website;		}		// Create wrapper element for styling login inputs		$login_inputs = new HTMLTag ('div', array (			'class' => 'hashover-inputs'		));		// Create and append login input elements to main form inputs wrapper element		foreach ($login_input_attributes as $field => $attributes) {			// Skip disabled input tags			if ($this->setup->formFields[$field] === 'off') {				continue;			}			// Create cell element for inputs			$input_cell = new HTMLTag ('div', array (				'class' => 'hashover-input-cell'			));			// Check if form labels are enabled			if ($this->setup->usesLabels === true) {				// If so, create label element for input				$label = new HTMLTag ('label', array (					'for' => $attributes['input-id'],					'class' => $attributes['label-class'],					'innerHTML' => $attributes['placeholder']				), false);				// Add label to cell element				$input_cell->appendChild ($label);			}			// Add a class for indicating a required field			if ($this->setup->formFields[$field] === 'required') {				$input_cell->appendAttribute ('class', 'hashover-required-input');			}			// Create wrapper element for input			$input_wrapper = new HTMLTag ('div', array (				'class' => $attributes['wrapper-class']			));			// Add a class for indicating a post failure			if ($this->emphasizedField === $field) {				$input_wrapper->appendAttribute ('class', 'hashover-emphasized-input');			}			// Create input element			$input = new HTMLTag ('input', array (				'id' => $attributes['input-id'],				'class' => 'hashover-input-info',				'type' => $attributes['input-type'],				'name' => $attributes['input-name'],				'value' => $attributes['input-value'],				'autocomplete' => $attributes['autocomplete'],				'title' => $attributes['input-title'],				'placeholder' => $attributes['placeholder']			), false, true);			// Add input to wrapper element			$input_wrapper->appendChild ($input);			// Add input to cell element			$input_cell->appendChild ($input_wrapper);			// Add input cell to main inputs wrapper element			$login_inputs->appendChild ($input_cell);		}		return $login_inputs;	}	// Creates avatar element	protected function avatar ($text)	{		// If avatars set to images		if ($this->setup->iconMode === 'image') {			// Logged in			if ($this->login->userIsLoggedIn === true) {				// Image source is avatar image				$hash = !empty ($this->login->email) ? md5 (mb_strtolower (trim ($this->login->email))) : '';				$avatar_src = $this->avatars->getGravatar ($hash);			} else {				// Logged out				// Image source is local default image				$avatar_src = $this->setup->getImagePath ('first-comment');			}			// Create avatar image element			$avatar = new HTMLTag ('div', array (				'style' => 'background-image: url(\'' . $avatar_src . '\');'			), false);		} else {			// Avatars set to count			// Create element displaying comment number user will be			$avatar = new HTMLTag ('span', $text, false);		}		return $avatar;	}	// Creates "Notify me of replies" checkbox	protected function subscribeLabel ($permalink = '', $type = 'main', $checked = true)	{		// Reply/edit form indicator		$is_form = !empty ($permalink);		// The checkbox ID		$id = $this->prefix ($type . '-subscribe', $is_form);		// Append permalink to the ID if one was given		if ($is_form === true) {			$id .= '-' . $permalink;		}		// Create subscribe checkbox label element		$subscribe_label = new HTMLTag ('label', array (			'for' => $id,			'class' => 'hashover-' . $type . '-label',			'title' => $this->locale->text['subscribe-tip']		));		// Create subscribe element checkbox		$subscribe = new HTMLTag ('input', array (			'id' => $id,			'type' => 'checkbox',			'name' => 'subscribe'		), false, true);		// Tick the checkbox		if ($checked === true) {			$subscribe->createAttribute ('checked', 'true');		}		// Add subscribe checkbox element to subscribe checkbox label element		$subscribe_label->appendChild ($subscribe);		// Add text to subscribe checkbox label element		$subscribe_label->appendInnerHTML ($this->locale->text['subscribe']);		return $subscribe_label;	}	// Creates a table-like cell for the allowed HTML/markdown panel	protected function formatCell ($format, $locale_key)	{		// Create cell title		$title = new HTMLTag ('p', array ('class' => 'hashover-title'));		// "Allowed HTML/Markdown" locale string		$title->innerHTML ($this->locale->text['allowed-' . $format]);		// Create allowed HTML/markdown text paragraph		$paragraph = new HTMLTag ('p', array (			'innerHTML' => $this->locale->text[$locale_key])		);		// And return both elements in a <div> tag		return new HTMLTag ('div', array (			'children' => array ($title, $paragraph)		));	}	// Creats a comment form, ie. main/reply/edit	protected function commentForm (HTMLTag $form, $type, $placeholder, $text, $permalink = '')	{		// Reply/edit form indicator		$is_form = !empty ($permalink);		// Prepend dash to permalink if present		$permalink = $is_form ? '-' . $permalink : '';		// Form title locale key		$title_locale = ($type === 'reply') ? 'reply-form' : 'comment-form';		// Create textarea		$textarea = new HTMLTag ('textarea', array (			'id' => $this->prefix ($type . '-comment' . $permalink, $is_form),			'class' => 'hashover-textarea hashover-' . $type . '-textarea',			'cols' => '63',			'name' => 'comment',			'rows' => '6',			'title' => $this->locale->text[$title_locale]		), false);		// Set the placeholder attribute if one is given		if (!empty ($placeholder)) {			$textarea->createAttribute ('placeholder', $placeholder);		}		if ($type === 'main') {			// Add a class for indicating a post failure			if ($this->emphasizedField === 'comment') {				$textarea->appendAttribute ('class', 'hashover-emphasized-input');			}			// If the comment was a reply, have the textarea use the reply textarea locale			if ($this->cookies->getValue ('replied') !== '') {				$reply_form_placeholder = $this->locale->text['reply-form'];				$textarea->createAttribute ('placeholder', $reply_form_placeholder);			}		}		// Set textarea content if given any text		if (!empty ($text)) {			$textarea->innerHTML ($text);		}		// Add textarea element to form element		$form->appendChild ($textarea);		// Create element for various messages when needed		if ($type !== 'main') {			$message = new HTMLTag ('div', array (				'id' => $this->prefix ($type . '-message-container' . $permalink, $is_form),				'class' => 'hashover-message',				'children' => array (					new HTMLTag ('div', array (						'id' => $this->prefix ($type . '-message' . $permalink, $is_form)					))				)			));			// Add message element to form element			$form->appendChild ($message);		}		// Create allowed HTML message element		$allowed_formatting_message = new HTMLTag ('div', array (			'id' => $this->prefix ($type . '-formatting-message' . $permalink, $is_form),			'class' => 'hashover-formatting-message'		));		// Create formatting table		$allowed_formatting_table = new HTMLTag ('div', array (			'class' => 'hashover-formatting-table',			'children' => array (				$this->formatCell ('html', 'html-format')			)		));		// Append Markdown cell if Markdown is enabled		if ($this->setup->usesMarkdown !== false) {			$markdown_cell = $this->formatCell ('markdown', 'markdown-format');			$allowed_formatting_table->appendChild ($markdown_cell);		}		// Append formatting table to formatting message		$allowed_formatting_message->appendChild ($allowed_formatting_table);		// Ensure the allowed HTML message is open in PHP mode		if ($this->mode === 'php') {			$allowed_formatting_message->appendAttribute ('class', 'hashover-message-open');			$allowed_formatting_message->appendAttribute ('class', 'hashover-php-message-open');		}		// Add allowed HTML message element to form element		$form->appendChild ($allowed_formatting_message);	}	// Creates hidden page info fields, ie. page URL, title, reply comment	protected function pageInfoFields (HTMLTag $form, $url = '{url}', $thread = '{thread}', $title = '{title}')	{		// Create hidden page URL input element		$url_input = new HTMLTag ('input', array (			'type' => 'hidden',			'name' => 'url',			'value' => $url		), false, true);		// Add hidden page URL input element to form element		$form->appendChild ($url_input);		// Create hidden comment thread input element		$thread_input = new HTMLTag ('input', array (			'type' => 'hidden',			'name' => 'thread',			'value' => $thread		), false, true);		// Add hidden comments thread input element to form element		$form->appendChild ($thread_input);		// Create hidden page title input element		$title_input = new HTMLTag ('input', array (			'type' => 'hidden',			'name' => 'title',			'value' => $title		), false, true);		// Add hidden page title input element to form element		$form->appendChild ($title_input);		// Check if the script is being remotely accessed		if ($this->setup->remoteAccess === true) {			// Create hidden input element indicating remote access			$remote_access_input = new HTMLTag ('input', array (				'type' => 'hidden',				'name' => 'remote-access',				'value' => 'true'			), false, true);			// Add remote access input element to form element			$form->appendChild ($remote_access_input);		}		// Check if config queries are present		if (!empty ($this->setup->cfgQueries)) {			// If so, run through config queries			foreach ($this->setup->cfgQueries as $key => $value) {				// Add hidden input to form for each config query				$form->appendChild (new HTMLTag ('input', array (					'type' => 'hidden',					'name' => sprintf ('cfg[%s]', $key),					'value' => $value				)));			}		}	}	// Creates "Formatting" link to open the allowed HTML/markdown panel	protected function formatting ($type, $permalink = '')	{		// Reply/edit form indicator		$is_form = !empty ($permalink);		// Prepend dash to permalink if present		$permalink = $is_form ? '-' . $permalink : '';		// "Formatting" hyperlink locale		$allowed_format = $this->locale->text['comment-formatting'];		// Create allowed HTML message revealer hyperlink		$allowed_formatting = new HTMLTag ('span', array (			'id' => $this->prefix ($type . '-formatting' . $permalink, $is_form),			'class' => 'hashover-fake-link hashover-formatting',			'title' => $allowed_format,			'innerHTML' => $allowed_format		));		// Return the hyperlink		return $allowed_formatting;	}	// Re-encode a URL	protected function safeURLEncode ($url)	{		return urlencode (urldecode ($url));	}	protected function commentsShouldBeClosed() {		if ($this->setup->commentsClosed === true) {			return true;		}		if ($this->setup->commentLimit !== 0 &&				$this->commentCounts['total'] > $this->setup->commentLimit) {				return true;		}		return false;	}	// Creates the main HashOver element	protected function createMainElement ()	{		// Create element that HashOver comments will appear in		$main = new HTMLTag ('div', array (			'id' => $this->prefix (),			'class' => 'hashover'		), false);		// Add class indictating desktop and mobile styling		if ($this->setup->isMobile === true) {			$main->appendAttribute ('class', 'hashover-mobile');		} else {			$main->appendAttribute ('class', 'hashover-desktop');		}		// Add class for raster or vector images		if ($this->setup->imageFormat === 'svg') {			$main->appendAttribute ('class', 'hashover-vector');		} else {			$main->appendAttribute ('class', 'hashover-raster');		}		// Add class to indicate user login status		if ($this->login->userIsLoggedIn === true) {			$main->appendAttribute ('class', 'hashover-logged-in');		} else {			$main->appendAttribute ('class', 'hashover-logged-out');		}		// Create element for jump anchor		if ($this->setup->instanceNumber === 1) {			$jump_anchor = new HTMLTag ('div', array (				'id' => 'comments'			));			// Add jump anchor to HashOver element			$main->appendChild ($jump_anchor);		}		return $main;	}	// Creates initial HTML elements, such as comment form and end links	public function initialHTML ($hashover_wrapper = true)	{		// Create main HashOver element		$hashover_element = $this->createMainElement ();		// Check if a login is required and user is not logged in		if ($this->setup->requiresLogin === true		    and $this->login->userIsLoggedIn === false)		{			// If so, create required login message element			$hashover_element->appendChild (new HTMLTag ('div', array (				'class' => 'hashover-requires-login-message',				'innerHTML' => $this->locale->text['login-required']			)));			// Return all HTML with the HashOver wrapper element			if ($hashover_wrapper === true) {				return $hashover_element->asHTML ();			}			// Return just the HashOver wrapper element's innerHTML			return $hashover_element->innerHTML;		}		// Create primary form wrapper element		$form_section = new HTMLTag ('div', array (			'id' => $this->prefix ('form-section', false),			'class' => 'hashover-form-section'		));		// Hide primary form wrapper if comments are to be initially hidden		if ($this->mode !== 'php' and $this->setup->collapsesInterface === true) {			$form_section->createAttribute ('style', 'display: none;');		}		// Create element for "Post Comment" title		$post_title = new HTMLTag ('span', array (			'class' => array (				'hashover-title',				'hashover-main-title',				'hashover-dashed-title'			),			'innerHTML' => $this->postComment		));		// Add "Post Comment" element to primary form wrapper		if ($this->commentsShouldBeClosed() === false) {			$form_section->appendChild ($post_title);		}		// Create element for various messages when needed		$message_container = new HTMLTag ('div', array (			'id' => $this->prefix ('message-container', false),			'class' => 'hashover-title hashover-message'		));		// Create element for message text		$message_element = new HTMLTag ('div', array (			'id' => $this->prefix ('message', false),			'class' => 'hashover-main-message'		));		// Check if message cookie is set		if ($this->cookies->getValue ('message') !== ''		    or $this->cookies->getValue ('error') !== '')		{			// If so, set the message element to open in PHP mode			if ($this->mode === 'php') {				$message_container->appendAttribute ('class', array (					'hashover-message-open',					'hashover-php-message-open'				));			}			// Check if the message is a normal message			if ($this->cookies->getValue ('message') !== '') {				// If so, get an XSS safe version of the message				$message = Misc::makeXSSsafe ($this->cookies->getValue ('message'));			} else {				// If not, get an XSS safe version of the error message				$message = Misc::makeXSSsafe ($this->cookies->getValue ('error'));				// And set a class to the message element indicating an error				$message_container->appendAttribute ('class', 'hashover-message-error');			}			// And put current message into message element			$message_element->innerHTML ($message);		}		// Add message text element to message element		$message_container->appendChild ($message_element);		// Add message element to primary form wrapper		$form_section->appendChild ($message_container);		// Create main HashOver form		$main_form = new HTMLTag ('form', array (			'id' => $this->prefix ('form', false),			'class' => 'hashover-form hashover-balloon',			'name' => 'hashover-form',			'action' => $this->setup->getBackendPath ('form-actions.php'),			'method' => 'post'		));		// Create wrapper element for styling inputs		$main_inputs = new HTMLTag ('div', array (			'class' => 'hashover-inputs'		));		// If avatars are enabled		if ($this->setup->iconMode !== 'none') {			// Create avatar element for main HashOver form			$main_avatar = new HTMLTag ('div', array (				'class' => 'hashover-avatar-image'			));			// Add count element to avatar element			$main_avatar->appendChild ($this->avatar ($this->commentCounts['primary']));			// Add avatar element to inputs wrapper element			$main_inputs->appendChild ($main_avatar);		}		// Logged in		if ($this->login->userIsLoggedIn === true) {			if (!empty ($this->login->name)) {				$user_name = Misc::makeXSSsafe ($this->login->name);			} else {				$user_name = $this->setup->defaultName;			}			$user_website = Misc::makeXSSsafe ($this->login->website);			$name_class = 'hashover-name-plain';			$is_twitter = false;			// Check if user's name is a Twitter handle			if ($user_name[0] === '@') {				$user_name = mb_substr ($user_name, 1);				$name_class = 'hashover-name-twitter';				$is_twitter = true;				$name_length = mb_strlen ($user_name);				if ($name_length > 1 and $name_length <= 30) {					if (empty ($user_website)) {						$user_website = 'https://twitter.com/' . $user_name;					}				}			}			// Create element for logged user's name			$main_form_column_spanner = new HTMLTag ('div', array (				'class' => 'hashover-comment-name hashover-top-name'			), false);			// Check if user gave website			if (!empty ($user_website)) {				if ($is_twitter === false) {					$name_class = 'hashover-name-website';				}				// Create link to user's website				$main_form_hyperlink = new HTMLTag ('a', array (					'rel' => 'noopener noreferrer nofollow',					'href' => $user_website,					'target' => '_blank',					'title' => $user_name,					'innerHTML' => $user_name				), false);				// Add username hyperlink to main form column spanner				$main_form_column_spanner->appendChild ($main_form_hyperlink);			} else {				// No website				$main_form_column_spanner->innerHTML ($user_name);			}			// Set classes user's name spanner			$main_form_column_spanner->appendAttribute ('class', $name_class);			// Add main form column spanner to inputs wrapper element			$main_inputs->appendChild ($main_form_column_spanner);		} else {			// Logged out			// Use default login inputs			$main_inputs->appendInnerHTML ($this->defaultLoginInputs->innerHTML);		}		// Add inputs wrapper to form element		$main_form->appendChild ($main_inputs);		// Create fake "required fields" element as a SPAM trap		$required_fields = new HTMLTag ('div', array (			'class' => 'hashover-required-fields'		));		// Fake "required fields" to create		$fake_fields = array (			'summary' => 'text',			'age' => 'hidden',			'lastname' => 'text',			'address' => 'text',			'zip' => 'hidden',		);		// Create and append fake input elements to fake required fields		foreach ($fake_fields as $name => $type) {			$fake_input = new HTMLTag ('input', array (				'type' => $type,				'name' => $name,				'value' => ''			), false, true);			// Add fake summary input element to fake required fields			$required_fields->appendInnerHTML ($fake_input->asHTML ());		}		// Add fake input elements to form element		$main_form->appendChild ($required_fields);		// Post button locale		$post_button = $this->locale->text['post-button'];		// Check if form labels are enabled		if ($this->setup->usesLabels === true) {			// If so, create label element for comment textarea			$main_comment_label = new HTMLTag ('label', array (				'for' => 'hashover-main-comment',				'class' => 'hashover-comment-label',				'innerHTML' => $post_button			), false);			// Add comment label to form element			$main_form->appendChild ($main_comment_label);		}		// Get comment text if a comment cookie is set		$comment_text = Misc::makeXSSsafe ($this->cookies->getValue ('comment'));		// Comment form placeholder text		$comment_form = $this->locale->text['comment-form'];		// Create main textarea element and add it to form element		$this->commentForm ($main_form, 'main', $comment_form, $comment_text);		// Add page info fields to main form		$this->pageInfoFields ($main_form, $this->setup->pageURL, $this->setup->threadName, $this->setup->pageTitle);		// Check if comment is a failed reply		if ($this->cookies->getValue ('replied') !== '') {			// If so, get the comment being replied to			$replied = $this->cookies->getValue ('replied');			// Create hidden reply to input element			$reply_to_input = new HTMLTag ('input', array (				'type' => 'hidden',				'name' => 'reply-to',				'value' => Misc::makeXSSsafe ($replied)			), false, true);			// And add hidden reply to input element to form element			$main_form->appendChild ($reply_to_input);		}		// Create wrapper element for main form footer		$main_form_footer = new HTMLTag ('div', array (			'class' => 'hashover-form-footer'		));		// Create wrapper for form links		$main_form_links_wrapper = new HTMLTag ('span', array (			'class' => 'hashover-form-links'		));		// Add checkbox label element to main form buttons wrapper element		if ($this->setup->emailField !== 'off' && in_array($this->setup->sendsNotifications, ['to-everyone', 'to-users'])) {			if ($this->login->userIsLoggedIn === false or !empty ($this->login->email)) {				$subscribed = ($this->setup->subscribesUser === true);				$subscribe_label = $this->subscribeLabel ('', 'main', $subscribed);				$main_form_links_wrapper->appendChild ($subscribe_label);			}		}		// Create and add allowed HTML revealer hyperlink		if ($this->setup->allowHTML === true && $this->mode !== 'php') {			$main_form_links_wrapper->appendChild ($this->formatting ('main'));		}		// Add main form links wrapper to main form footer element		$main_form_footer->appendChild ($main_form_links_wrapper);		// Create wrapper for form buttons		$main_form_buttons_wrapper = new HTMLTag ('span', array (			'class' => 'hashover-form-buttons'		));		// Create "Login" / "Logout" button element		if ($this->setup->allowsLogin !== false or $this->login->userIsLoggedIn === true) {			$login_button = new HTMLTag ('input', array (				'id' => $this->prefix ('login-button', false),				'class' => 'hashover-submit',				'type' => 'submit'			), false, true);			// Check login state			if ($this->login->userIsLoggedIn === true) {				// Logged in				$login_button->appendAttribute ('class', 'hashover-logout');				$logout_locale = $this->locale->text['logout'];				// Create logged in attributes				$login_button->createAttributes (array (					'name' => 'logout',					'value' => $logout_locale,					'title' => $logout_locale				));			} else {				// Logged out				$login_button->appendAttribute ('class', 'hashover-login');				// Create logged out attributes				$login_button->createAttributes (array (					'name' => 'login',					'value' => $this->locale->text['login'],					'title' => $this->locale->text['login-tip']				));			}			// Add "Login" / "Logout" element to main form footer element			$main_form_buttons_wrapper->appendChild ($login_button);		}		// Create "Post Comment" button element		$main_post_button = new HTMLTag ('input', array (			'id' => $this->prefix ('post-button', false),			'class' => 'hashover-submit hashover-post-button',			'type' => 'submit',			'name' => 'post',			'value' => $post_button,			'title' => $post_button		), false, true);		$moderation_warning = new HTMLTag ('p', array(			'class' => 'hashover-moderation-warning',			'innerHTML' => 'Your comment will be held for moderation.'		));		// Add "Post Comment" element to main form buttons wrapper element		$main_form_buttons_wrapper->appendChild ($main_post_button);		// Add main form button wrapper to main form footer element		$main_form_footer->appendChild ($main_form_buttons_wrapper);		if ($this->setup->usesModeration === true) {			$main_form_footer->appendChild ($moderation_warning);		}		// Add main form footer to main form element		$main_form->appendChild ($main_form_footer);		// Add main form element to primary form wrapper		if ($this->commentsShouldBeClosed() === true) {			$comments_closed_message = new HTMLTag ('p', array(				'class' => 'hashover-comments-closed-message',				'innerHTML' => 'Comments are closed.'			));			$form_section->appendChild ($comments_closed_message);		} else {			$form_section->appendChild ($main_form);		}		// Check if form position setting set to 'top'		if ($this->setup->formPosition !== 'bottom') {			// Add primary form wrapper to HashOver element			$hashover_element->appendChild ($form_section);		}		if ($this->commentCounts['popular'] > 0) {			// Create wrapper element for popular comments			$popular_section = new HTMLTag ('div', array (				'id' => $this->prefix ('popular-section', false),				'class' => 'hashover-popular-section'			), false);			// Hide popular comments wrapper if comments are to be initially hidden			if ($this->mode !== 'php') {				if ($this->setup->collapsesInterface === true or $this->setup->collapseLimit <= 0) {					$popular_section->createAttribute ('style', 'display: none;');				}			}			// Create wrapper element for popular comments title			$pop_count_wrapper = new HTMLTag ('div', array (				'class' => 'hashover-dashed-title'			));			// Create element for popular comments title			$pop_count_element = new HTMLTag ('span', array (				'class' => 'hashover-title'			));			// Check if there is more than one popular comment			if ($this->commentCounts['popular'] !== 1) {				// If so, use "Most Popular Comments" locale string				$pop_count_element->innerHTML ($this->locale->text['most-popular-comments']);			} else {				// If not, use "Most Popular Comment" locale string				$pop_count_element->innerHTML ($this->locale->text['most-popular-comment']);			}			// Add popular comments title element to wrapper element			$pop_count_wrapper->appendChild ($pop_count_element);			// Add popular comments title wrapper element to popular comments section			$popular_section->appendChild ($pop_count_wrapper);			// Create element for popular comments to appear in			$popular_comments = new HTMLTag ('div', array (				'id' => $this->prefix ('top-comments', false),				'class' => 'hashover-top-comments'			), false);			// Add comments to HashOver element			if (!empty ($this->popularComments)) {				$popular_comments->innerHTML (trim ($this->popularComments));			}			// Add popular comments element to popular comments section			$popular_section->appendChild ($popular_comments);			// Add popular comments section to HashOver element			$hashover_element->appendChild ($popular_section);		}		// Create wrapper element for comments		$comments_section = new HTMLTag ('div', array (			'id' => $this->prefix ('comments-section', false),			'class' => 'hashover-comments-section'		), false);		// Create wrapper element for comment count and sort dropdown menu		$count_sort_wrapper = new HTMLTag ('div', array (			'id' => $this->prefix ('count-wrapper', false),			'class' => 'hashover-count-sort hashover-dashed-title'		));		// Create element for comment count		$count_element = new HTMLTag ('span', array (			'id' => $this->prefix ('count', false),			'class' => 'hashover-count'		));		// Add comment count to comment count element		if ($this->commentCounts['total'] > 1) {			$count_element->innerHTML ($this->commentCounts['show-count']);		}		// Add comment count element to wrapper element		$count_sort_wrapper->appendChild ($count_element);		// Hide comment count if there are no comments		if ($this->commentCounts['total'] <= 1) {			$count_sort_wrapper->createAttribute ('style', 'display: none;');		}		// JavaScript mode specific HTML		if ($this->mode !== 'php') {			// Hide wrapper if comments are to be initially hidden			if ($this->setup->collapsesInterface === true) {				$comments_section->createAttribute ('style', 'display: none;');			}			// Hide comment count if collapse limit is set to zero			if ($this->setup->collapseLimit <= 0) {				$count_sort_wrapper->createAttribute ('style', 'display: none;');			}			// Check if there is more than one comment			if ($this->setup->showSortOptions === true && 					$this->commentCounts['total'] > 2) {				// If so, create wrapper element for sort dropdown menu				$sort_wrapper = new HTMLTag ('span', array (					'id' => $this->prefix ('sort', false),					'class' => 'hashover-select-wrapper hashover-sort-select'				));				// Create sort dropdown menu element				$sort_select = new HTMLTag ('select', array (					'id' => $this->prefix ('sort-select', false),					'name' => 'sort',					'size' => '1',					'title' => $this->locale->text['sort']				));				// Array of select tag sort options				$sort_options = array (					'ascending'	=> $this->locale->text['sort-ascending'],					'descending'	=> $this->locale->text['sort-descending'],					'by-date'	=> $this->locale->text['sort-by-date'],					'by-likes'	=> $this->locale->text['sort-by-likes'],					'by-replies'	=> $this->locale->text['sort-by-replies'],					'by-name'	=> $this->locale->text['sort-by-name']				);				// Run through each sort option				foreach ($sort_options as $value => $html) {					// Create option for sort dropdown menu element					$option = new HTMLTag ('option', array (						'value' => $value,						'innerHTML' => $html					), false);					// Set option as selected if its the configured default					if ($value === $this->setup->defaultSorting) {						$option->createAttribute ('selected', 'selected');					}					// Add sort option element to sort dropdown menu					$sort_select->appendChild ($option);				}				// Create empty option group as spacer				$spacer_optgroup = new HTMLTag ('optgroup', array (					'label' => '&nbsp;'				));				// Add spacer option group to sort dropdown menu				$sort_select->appendChild ($spacer_optgroup);				// Create option group for threaded sort options				$threaded_optgroup = new HTMLTag ('optgroup', array (					'label' => $this->locale->text['threads']				));				// Array of select tag threaded sort options				$threaded_sort_options = array (					'threaded-descending'	=> $this->locale->text['sort-descending'],					'threaded-by-date'	=> $this->locale->text['sort-by-date'],					'threaded-by-likes'	=> $this->locale->text['sort-by-likes'],					'by-popularity'		=> $this->locale->text['sort-by-popularity'],					'by-discussion'		=> $this->locale->text['sort-by-discussion'],					'threaded-by-name'	=> $this->locale->text['sort-by-name']				);				// Run through each threaded sort option				foreach ($threaded_sort_options as $value => $html) {					// Create option for sort dropdown menu element					$option = new HTMLTag ('option', array (						'value' => $value,						'innerHTML' => $html					), false);					// Set option as selected if its the configured default					if ($value === $this->setup->defaultSorting) {						$option->createAttribute ('selected', 'selected');					}					// Add sort option element to threaded option group					$threaded_optgroup->appendChild ($option);				}				// Add threaded sort options group to sort dropdown menu				$sort_select->appendChild ($threaded_optgroup);				// Add sort dropdown menu element to sort wrapper element				$sort_wrapper->appendChild ($sort_select);				// Add comment count element to wrapper element				$count_sort_wrapper->appendChild ($sort_wrapper);			}		}		// Add comment count and sort dropdown menu wrapper to comments section		$comments_section->appendChild ($count_sort_wrapper);		// Create element that will hold the comments		$sort_div = new HTMLTag ('div', array (			'id' => $this->prefix ('sort-section', false),			'class' => 'hashover-sort-section'		), false);		// Add comments to HashOver element		if (!empty ($this->comments)) {			$sort_div->innerHTML (trim ($this->comments));		}		// Add comments element to comments section		$comments_section->appendChild ($sort_div);		// Add comments element to HashOver element		$hashover_element->appendChild ($comments_section);		// Check if form position setting set to 'bottom'		if ($this->setup->formPosition === 'bottom') {			// Add primary form wrapper to HashOver element			$hashover_element->appendChild ($form_section);		}		// Create end links wrapper element		$end_links_wrapper = new HTMLTag ('div', array (			'id' => $this->prefix ('end-links', false),			'class' => 'hashover-end-links'		));		// Hide end links wrapper if comments are to be initially hidden		if ($this->mode !== 'php' and $this->setup->collapsesInterface === true) {			$end_links_wrapper->createAttribute ('style', 'display: none;');		}		// HashOver Comments hyperlink text		$homepage_link_text = $this->locale->text['hashover-comments'];		// Create link back to HashOver homepage		$homepage_link = new HTMLTag ('a', array (			'rel' => 'nofollow',			'href' => 'https://www.barkdull.org/software/hashover',			'class' => 'hashover-home-link',			'target' => '_blank',			'title' => $homepage_link_text,			'innerHTML' => $homepage_link_text		), false);		// Add link back to HashOver homepage to end links wrapper element		$end_links_wrapper->innerHTML ($homepage_link->asHTML () . ' &#8210;');		// End links array		$end_links = array ();		// Check if there is at least one comment		if ($this->commentCounts['total'] > 1) {			// If so, check if we are to append an RSS feed link			if ($this->setup->appendsRss === true) {				// If so, encode page URL				$page_url = $this->safeURLEncode ($this->setup->pageURL);				// Create RSS feed link				$rss_link = new HTMLTag ('a', array (), false);				$rss_link->createAttribute ('href', $this->setup->getHttpPath ('api/rss.php'));				$rss_link->appendAttribute ('href', '?url=' . $page_url, false);				// "RSS Feed" locale string				$rss_link_text = $this->locale->text['rss-feed'];				// Create RSS feed attributes				$rss_link->createAttributes (array (					'class' => 'hashover-rss-link',					'rel' => 'nofollow',					'target' => '_blank',					'title' => $rss_link_text,					'innerHTML' => $rss_link_text				));				// Add RSS hyperlink to end links array				$end_links[] = $rss_link->asHTML ();			}		}		// Source Code hyperlink text		$source_link_text = $this->locale->text['source-code'];		// Create link to HashOver source code		$source_link = new HTMLTag ('a', array (			'rel' => 'nofollow',			'href' => $this->setup->getBackendPath ('source-viewer.php'),			'class' => 'hashover-source-link',			'target' => '_blank',			'title' => $source_link_text,			'innerHTML' => $source_link_text		), false);		// Add source code hyperlink to end links array		$end_links[] = $source_link->asHTML ();		if ($this->mode !== 'php') {			// Create link to HashOver JavaScript source code			$javascript_link = new HTMLTag ('a', array (				'rel' => 'nofollow',				'href' => $this->setup->getHttpPath ('comments.php'),				'class' => 'hashover-javascript-link',				'target' => '_blank',				'title' => 'JavaScript'			), false);			// Add JavaScript code hyperlink text			$javascript_link->innerHTML ('JavaScript');			// Add JavaScript hyperlink to end links array			$end_links[] = $javascript_link->asHTML ();		}		// Add end links to end links wrapper element		$end_links_wrapper->appendInnerHTML (implode (' &middot;' . PHP_EOL, $end_links));		// Add end links wrapper element to HashOver element		$hashover_element->appendChild ($end_links_wrapper);		// Return all HTML with the HashOver wrapper element		if ($hashover_wrapper === true) {			return $hashover_element->asHTML ();		}		// Return just the HashOver wrapper element's innerHTML		return $hashover_element->innerHTML;	}}