<?php
/**
 * When using AngularJS models, sometimes variable types are added to the input's value (ie: number:5, string:xxx).
 * If you use the model normally, this is transparent.
 * However, if you submit the form normally (without AJAX), this junk gets submitted.
 */
function cleanAngularGarbage($value)
{
	return preg_replace('/^(number|string):/', "", $value);
}

function angularYesNo($name, $model, $default, $on="Yes", $off="No")
{
	?>
	<div class="btn-group" role="group">
		<button type="button" class="btn" ng-class="{'btn-success':<?php echo($model); ?>==1,'btn-secondary':<?php echo($model); ?>!=1}" ng-click="<?php echo($model); ?>=1"><?php echo($on); ?></button>
		<button type="button" class="btn" ng-class="{'btn-success':<?php echo($model); ?>==0,'btn-secondary':<?php echo($model); ?>!=0}" ng-click="<?php echo($model); ?>=0"><?php echo($off); ?></button>
	</div><input type="text" id="<?php echo($name); ?>" name="<?php echo($name); ?>" class="toggle_value toggle_value_<?php echo($name); ?>" tabindex="-1" ng-model="<?php echo($model); ?>" data-default="<?php echo($default); ?>" required /><?php
}

function yesNo($name, $value, $class="", $on="Yes", $off="No")
{
	$model = str_replace("-", "", $name);
	?>
	<div class="btn-group" role="group" ng-init="<?php echo($model); ?>='<?php echo($value); ?>'">
		<button type="button" class="btn <?php echo($class); ?>-yes" ng-class="{'btn-success':<?php echo($model); ?>==1,'btn-secondary':<?php echo($model); ?>!=1}" ng-click="<?php echo($model); ?>=1"><?php echo($on); ?></button>
		<button type="button" class="btn <?php echo($class); ?>-no" ng-class="{'btn-success':<?php echo($model); ?>==0,'btn-secondary':<?php echo($model); ?>!=0}" ng-click="<?php echo($model); ?>=0"><?php echo($off); ?></button>
	</div><input type="text" id="<?php echo($name); ?>" name="<?php echo($name); ?>" class="toggle_value toggle_value_<?php echo($name); ?>" tabindex="-1" ng-model="<?php echo($model); ?>" required /><?php
}

function angularMultipleChoice($name, $model, $default, $options, $onclick="")
{
	?>
	<div class="btn-group" role="group">
		<?php
		foreach($options as $key=>$label)
		{
			?><a href="" onclick="return false" class="btn btn-<?php echo($name); ?>" ng-class="{'btn-success':<?php echo($model); ?>=='<?php echo($key); ?>','btn-secondary':<?php echo($model); ?>!='<?php echo($key); ?>'}" ng-click="<?php echo($model); ?>='<?php echo($key); ?>';<?php echo($onclick); ?>"><?php echo($label); ?></a><?php
		}
		?>
	</div><input type="text" id="<?php echo($name); ?>" name="<?php echo($name); ?>" class="toggle_value toggle_value_<?php echo(str_replace(".", "", $name)); ?>" tabindex="-1" ng-model="<?php echo($model); ?>" data-default="<?php echo($default); ?>" required /><?php
}

function multipleChoice($name, $value, $options, $onclick="")
{
	$model = str_replace("-", "", $name);
	?>
	<div class="btn-group" role="group" ng-init="<?php echo($model); ?>='<?php echo($value); ?>'">
		<?php
		foreach($options as $key=>$label)
		{
			?><a href="" onclick="return false" class="btn btn-<?php echo($name); ?>" ng-class="{'btn-success':<?php echo($model); ?>=='<?php echo($key); ?>','btn-secondary':<?php echo($model); ?>!='<?php echo($key); ?>'}" ng-click="<?php echo($model); ?>='<?php echo($key); ?>';<?php echo($onclick); ?>"><?php echo($label); ?></a><?php
		}
		?>
	</div><input type="text" id="<?php echo($name); ?>" name="<?php echo($name); ?>" class="toggle_value toggle_value_<?php echo($name); ?>" tabindex="-1" ng-model="<?php echo($model); ?>" required /><?php
}

function angularMultipleChoiceVertical($name, $model, $default, $options, $onclick="")
{
	?>
	<div class="list-group" role="group">
		<?php
		foreach($options as $key=>$label)
		{
			?><a href="" onclick="return false" class="list-group-item list-group-item-action list-group-item-<?php echo($model); ?>" ng-class="{'list-group-item-success':<?php echo($model); ?>=='<?php echo($key); ?>'}" ng-click="<?php echo($model); ?>='<?php echo($key); ?>';<?php echo($onclick); ?>"><?php echo($label); ?></a><?php
		}
		?>
	</div><input type="text" id="<?php echo($name); ?>" name="<?php echo($name); ?>" class="toggle_value toggle_value_<?php echo($name); ?>" tabindex="-1" ng-model="<?php echo($model); ?>" data-default="<?php echo($default); ?>" required /><?php
}

function multipleChoiceVertical($name, $value, $options, $onclick="")
{
	$model = str_replace("-", "", $name);
	?>
	<div class="list-group" role="group" ng-init="<?php echo($model); ?>='<?php echo($value); ?>'">
		<?php
		foreach($options as $key=>$label)
		{
			?><a href="" onclick="return false" class="list-group-item list-group-item-action list-group-item-<?php echo($name); ?>" ng-class="{'list-group-item-success':<?php echo($model); ?>=='<?php echo($key); ?>'}" ng-click="<?php echo($model); ?>='<?php echo($key); ?>';<?php echo($onclick); ?>"><?php echo($label); ?></a><?php
		}
		?>
	</div><input type="text" id="<?php echo($name); ?>" name="<?php echo($name); ?>" class="toggle_value toggle_value_<?php echo($name); ?>" tabindex="-1" ng-model="<?php echo($model); ?>" required /><?php
}

function shop_sort_categories($category1, $category2)
{
	if($category1->category_path_string == $category2->category_path_string)
	{
		return 0;
	}
	return ($category1->category_path_string < $category2->category_path_string) ? -1 : 1;
}

function generatePassword($length=8, $chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*')
{
	$count = mb_strlen($chars);

	mt_srand();

	for ($i = 0, $result = ''; $i < $length; $i++) {
		$index = mt_rand(0, $count - 1);
		$result .= mb_substr($chars, $index, 1);
	}

	return $result;
}

/**
 * Truncates text.
 *
 * Cuts a string to the length of $length and replaces the last characters
 * with the ellipsis if the text is longer than length.
 *
 * ### Options:
 *
 * - `ellipsis` Will be used as ending and appended to the trimmed string
 * - `exact` If false, $text will not be cut mid-word
 * - `html` If true, HTML tags would be handled correctly
 * - `trimWidth` If true, $text will be truncated with the width
 *
 * @param string $text String to truncate.
 * @param int $length Length of returned string, including ellipsis.
 * @param array $options An array of HTML attributes and options.
 * @return string Trimmed string.
 * @link http://book.cakephp.org/3.0/en/core-libraries/string.html#truncating-text
 */
function truncate($text, $length = 100, array $options = [])
{
	$default = [
		'ellipsis' => '...', 'exact' => true, 'html' => false, 'trimWidth' => false,
	];
	if (!empty($options['html']) && strtolower(mb_internal_encoding()) === 'utf-8') {
		$default['ellipsis'] = "\xe2\x80\xa6";
	}
	$options += $default;

	$prefix = '';
	$suffix = $options['ellipsis'];
	$wiggle_room = $length * 0.10;

	if ($options['html']) {
		$ellipsisLength = _strlen(strip_tags($options['ellipsis']), $options);

		$truncateLength = 0;
		$totalLength = 0;
		$openTags = [];
		$truncate = '';

		preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER);
		foreach ($tags as $tag) {
			$contentLength = _strlen($tag[3], $options);

			if ($truncate === '') {
				if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/i', $tag[2])) {
					if (preg_match('/<[\w]+[^>]*>/', $tag[0])) {
						array_unshift($openTags, $tag[2]);
					} elseif (preg_match('/<\/([\w]+)[^>]*>/', $tag[0], $closeTag)) {
						$pos = array_search($closeTag[1], $openTags);
						if ($pos !== false) {
							array_splice($openTags, $pos, 1);
						}
					}
				}

				$prefix .= $tag[1];

				if ($totalLength + $contentLength + $ellipsisLength > $length) {
					$truncate = $tag[3];
					$truncateLength = $length - $totalLength;
				} else {
					$prefix .= $tag[3];
				}
			}

			$totalLength += $contentLength;
			if ($totalLength > $length) {
				break;
			}
		}

		if ($totalLength <= $length) {
			return $text;
		}

		$text = $truncate;
		$length = $truncateLength;

		foreach ($openTags as $tag) {
			$suffix .= '</' . $tag . '>';
		}
	} else {
		if (_strlen($text, $options) <= $length) {
			return $text;
		}
		$ellipsisLength = _strlen($options['ellipsis'], $options);
	}

	$chop = $length - $ellipsisLength;

	if ($options['exact'])
	{
		$result = _substr($text, 0, $chop, $options);

		// if (_substr($text, $chop, 1, $options) !== ' ') {
	 //        $result = _removeLastWord($result);
	 //    }

		// If result is empty, then we don't need to count ellipsis in the cut.
		if (!strlen($result)) {
			$result = _substr($text, 0, $length, $options);
		}
	}
	else
	{
		$period   = mb_strpos($text, ".", $chop);
		$bang     = mb_strpos($text, "!", $chop);
		$question = mb_strpos($text, "?", $chop);

		// Find the . or ! or ? closest to the chopping point.
		if($bang !== false && $bang < $period)
		{
			$period = $bang;
		}
		if($question !== false && $question < $period)
		{
			$period = $question;
		}

		if($period !== false && $chop != $period && ($period - $chop) <= $wiggle_room)
		{
			$chop   = $period + 1;
			$length = $chop;
		}

		$result = _substr($text, 0, $chop, $options);
	}

	// Remove the ellipsis if we stopped at the end of a sentence.
	if(preg_match('/[\.!\?]$/m', $result))
	{
		$suffix = preg_replace('/^' . $options['ellipsis'] . '/m', "", $suffix);
	}
	else if (!$options['exact'])
	{
		// Find the closest . or ! or ? to the end of the text and chop there.
		$period   = mb_strrpos($result, ".");
		$bang     = mb_strrpos($result, "!");
		$question = mb_strrpos($result, "?");

		if($bang !== false && $bang > $period)
		{
			$period = $bang;
		}
		if($question !== false && $question > $period)
		{
			$period = $question;
		}

		if($period !== false && ($length - $period) <= $wiggle_room)
		{
			$result = _substr($result, 0, $period + 1, $options);
			$suffix = preg_replace('/^' . $options['ellipsis'] . '/m', "", $suffix);
		}
	}

	return $prefix . $result . $suffix;
}

/**
 * Get string length.
 *
 * ### Options:
 *
 * - `html` If true, HTML entities will be handled as decoded characters.
 * - `trimWidth` If true, the width will return.
 *
 * @param string $text The string being checked for length
 * @param array $options An array of options.
 * @return int
 */
function _strlen($text, array $options)
{
	if (empty($options['trimWidth'])) {
		$strlen = 'mb_strlen';
	} else {
		$strlen = 'mb_strwidth';
	}

	if (empty($options['html'])) {
		return $strlen($text);
	}

	$pattern = '/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i';
	$replace = preg_replace_callback(
		$pattern,
		function ($match) use ($strlen) {
			$utf8 = html_entity_decode($match[0], ENT_HTML5 | ENT_QUOTES, 'UTF-8');

			return str_repeat(' ', $strlen($utf8, 'UTF-8'));
		},
		$text
	);

	return $strlen($replace);
}

/**
 * Return part of a string.
 *
 * ### Options:
 *
 * - `html` If true, HTML entities will be handled as decoded characters.
 * - `trimWidth` If true, will be truncated with specified width.
 *
 * @param string $text The input string.
 * @param int $start The position to begin extracting.
 * @param int $length The desired length.
 * @param array $options An array of options.
 * @return string
 */
function _substr($text, $start, $length, array $options)
{
	if (empty($options['trimWidth'])) {
		$substr = 'mb_substr';
	} else {
		$substr = 'mb_strimwidth';
	}

	$maxPosition = _strlen($text, ['trimWidth' => false] + $options);
	if ($start < 0) {
		$start += $maxPosition;
		if ($start < 0) {
			$start = 0;
		}
	}
	if ($start >= $maxPosition) {
		return '';
	}

	if ($length === null) {
		$length = _strlen($text, $options);
	}

	if ($length < 0) {
		$text = _substr($text, $start, null, $options);
		$start = 0;
		$length += _strlen($text, $options);
	}

	if ($length <= 0) {
		return '';
	}

	if (empty($options['html'])) {
		return (string)$substr($text, $start, $length);
	}

	$totalOffset = 0;
	$totalLength = 0;
	$result = '';

	$pattern = '/(&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};)/i';
	$parts = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
	foreach ($parts as $part) {
		$offset = 0;

		if ($totalOffset < $start) {
			$len = _strlen($part, ['trimWidth' => false] + $options);
			if ($totalOffset + $len <= $start) {
				$totalOffset += $len;
				continue;
			}

			$offset = $start - $totalOffset;
			$totalOffset = $start;
		}

		$len = _strlen($part, $options);
		if ($offset !== 0 || $totalLength + $len > $length) {
			if (strpos($part, '&') === 0 && preg_match($pattern, $part)
				&& $part !== html_entity_decode($part, ENT_HTML5 | ENT_QUOTES, 'UTF-8')
			) {
				// Entities cannot be passed substr.
				continue;
			}

			$part = $substr($part, $offset, $length - $totalLength);
			$len = _strlen($part, $options);
		}

		$result .= $part;
		$totalLength += $len;
		if ($totalLength >= $length) {
			break;
		}
	}

	return $result;
}

/**
 * Removes the last word from the input text.
 *
 * @param string $text The input text
 * @return string
 */
function _removeLastWord($text)
{
	$spacepos = mb_strrpos($text, ' ');

	if ($spacepos !== false) {
		$lastWord = mb_strrpos($text, $spacepos);

		// Some languages are written without word separation.
		// We recognize a string as a word if it doesn't contain any full-width characters.
		if (mb_strwidth($lastWord) === mb_strlen($lastWord)) {
			$text = mb_substr($text, 0, $spacepos);
		}

		return $text;
	}

	return '';
}

/**
 * Returns the english name of a month by number
 */
function Month($monthnumber){
  $month[1] = "January";
  $month[2] = "February";
  $month[3] = "March";
  $month[4] = "April";
  $month[5] = "May";
  $month[6] = "June";
  $month[7] = "July";
  $month[8] = "August";
  $month[9] = "September";
  $month[10] = "October";
  $month[11] = "November";
  $month[12] = "December";
  return $month[$monthnumber];
}//function

function FeralSortWidget($name, $min, $max, $value=NULL, $class=NULL, $style=NULL)
{
//TODO: Feral seems to puke using "min" and "max"
	$wgt = new FeralTextWidget($name, $value, $class, $style);
	$wgt->SetAttribute("type", "number");
	$wgt->SetAttribute("min", "$min");
	$wgt->SetAttribute("max", "$max");
	$wgt->SetAttribute("step", 1);

	/*for($i=$min; $i<=$max; $i++)
	{
		$wgt->AddOption("$i");
	}*/

	$wgt->Create();
}

function get_mailer()
{
	$CI = get_instance();
	$CI->load->library("encryption");

	$site 	= Mainframe::site();
	$mailer = new PHPMailer();
	$mailer->IsSMTP();
	$mailer->SMTPOptions = array
	(
		'ssl' => array(
			'verify_peer' => false,
			'verify_peer_name' => false,
			'allow_self_signed' => true
		)
	);
	$mailer->SMTPSecure		= $site->mail_tls;
	$mailer->SMTPAuth		= ($site->mail_username && $site->mail_password);
	$mailer->Timeout 		= 30;
	$mailer->SMTPKeepAlive 	= true;
	$mailer->Host			= $site->mail_host;
	$mailer->Port			= $site->mail_port;
	$mailer->Username		= $site->mail_username;
	$mailer->Password		= $CI->encryption->decrypt($site->mail_password);

	return $mailer;
}

function get_email_config()
{
	$config['protocol'] = 'smtp';
	$config['smtp_host'] = 'localhost';
	$config['smtp_user'] = '';
	$config['smtp_pass'] = '';
	$config['smtp_port'] = '587';
	$config['mailtype'] = 'html';

	return $config;
}

function get_id()
{
	$CI =& get_instance();

	if($CI->input->get("id"))
	{
		$id = $CI->input->get("id");
	}
	else if($CI->input->post("id"))
	{
		$id = $CI->input->post("id");
	}
	else
	{
		$id = 0;
	}

	return $id;
}

function add_shop_template($message)
{
	$template = file_get_contents(ABSOLUTE_PATH . "/templates/marketing/shop.html");
	$template = str_replace("[CONTENT]", $message, $template);

	return $template;
}

/**
 * Send a notification to one or more users.
 *
 * @access public
 * @param User $users
 * @param mixed $subject
 * @param mixed $message
 * @return void
 */
function send_notification($users, $subject, $message, $attachments=null)
{
	$CI =& get_instance();

	require_once(APPLICATION_PATH . "/third_party/class.smtp.php");
	require_once(APPLICATION_PATH . "/third_party/class.phpmailer.php");

	//ensure that $users is an array
	if(!is_array($users))
	{
		$users = array($users);
	}

	if(!isset($users) || count($users) == 0)
	{
		return false;
	}
	$users = array_unique($users);

	$site 	= Mainframe::site();
	$mailer = get_mailer();
    $mailer->ClearAllRecipients();
    $mailer->SetFrom($site->mail_username, ($site->mail_from ? $site->mail_from : COMPANY_NAME));
    $mailer->ClearReplyTos();
	$mailer->AddReplyTo(($site->mail_replyto ? $site->mail_replyto : COMPANY_EMAIL), ($site->mail_from ? $site->mail_from : COMPANY_NAME));

	foreach($users as $user)
	{
		if(isset($user->email))
		{
			$mailer->AddAddress($user->email);
		}
		else
		{
			//must be a string
			$mailer->AddAddress($user);
		}
	}

	if(isset($attachments))
	{
		if(is_array($attachments))
		{
			foreach($attachments as $attachment)
			{
				$mailer->AddAttachment($attachment);
			}
		}
		else
		{
			$mailer->AddAttachment($attachments);
		}
	}

	$message = add_marketing_template($message);

	$mailer->Subject = $subject;
	$mailer->MsgHTML($message);

	$result = $mailer->Send();

	$mailer->SMTPClose();

	return $result;
}

/**
 * Login is required to access the page where this is called.
 *
 * @access public
 * @return void
 */
function login_required()
{
	$CI = get_instance();
	$user = get_user();
	$page = new Page();
	$page->load(Mainframe::site()->login_page_id);

	if(!$user || $user->user_id <= 0 || !$user->has(ACTION_LOGIN))
	{
		$CI->load->helper('url');

		redirect("/" . $page->url() . "?redirect=" . rawurlencode(current_url()));
	}
}

/**
 * Admin login is required to access the page where this is called.
 *
 * @access public
 * @return void
 */
function admin_login_required()
{
	$CI = get_instance();
	$CI->load->helper('url');
	$user = get_user();

	if(defined("ADMIN_IP") && ADMIN_IP != "")
	{
		$ips = explode("|", ADMIN_IP);

		if(!in_array($CI->input->server("REMOTE_ADDR"), $ips))
		{
			redirect("/");
		}
	}

	if(!$user || $user->user_id <= 0 || !$user->has(ACTION_ADMIN_LOGIN))
	{
		redirect("/login?redirect=" . rawurlencode(current_url()));
	}
}

/**
 * Return the currently logged in User object, or false if nobody is logged in.
 *
 * @access public
 * @return void
 */
function get_user()
{
	static $user;
	$CI = get_instance();

	//already loaded
	if(isset($user->user_id))
	{
		return $user;
	}

	if($CI->input->cookie("user_id") != "")
	{
		$CI->load->library("encryption");

		$user = new User();
		$user->load($CI->encryption->decrypt($CI->input->cookie("user_id")));

		return $user;
	}
	else
	{
		$user 			= new User();
		$user->roles 	= array(1);
		$user->actions 	= array();
		return $user;
	}
}

/**
 * This function remains here for backwards compatibility with any custom modules which were using the function.
 * All core modules have been changed to get_user().
 * @return [type] [description]
 */
function get_user_logged_in()
{
	return get_user();
}

/**
 * Nicely dump strings or array/objects to the screen.
 *
 * @access public
 * @param mixed $message
 * @return void
 */
function devEcho($message)
{
	?><pre><?php
	if(is_array($message) || is_object($message))
	{
		print_r($message);
	}
	else
	{
		echo($message);
	}
	?></pre><?php
	echo("\n");
}

/**
 * Create a thumbnail of the desired size.
 * For backwards compatibility, creating a retina copy automatically is disabled by default since many areas do this manually already.
 * @param  String  $fileIn    Source file path.
 * @param  String  $fileOut   Destination file path.
 * @param  int 	   $reqWidth  Pixel width of the thumbnail.
 * @param  int     $reqHeight Pixel height of the thumbnail.
 * @param  boolean $retina    Automatically create a retina copy of this image if the source resolution is high enough (recommended).
 * @param  String  $crop 	  An aspect ratio and optionally crop position, each separated with a colon. ie: "4:3" or "4:3:top:left"
 *                            Possible values for crop position are top or left. Defaults to center.
 * @return void
 */
function createThumbnail($fileIn, $fileOut, $reqWidth, $reqHeight, $create_retina=false, $crop=false, $output_format=null)
{
	// If the input file doesn't exist, bail out to save resources.
	if(!file_exists($fileIn))
	{
		return true;
	}

	// Get image info.
	$size           = getimagesize($fileIn);
	$orig_width     = $size[0];
	$orig_height    = $size[1];
	$width          = $orig_width;
	$height         = $orig_height;
	$type           = $size[2];

	// Default to outputting the same file type received.
	if(!$output_format)
	{
		$output_format = $type;
	}

	// If WebP was requested but doesn't exist, output a PNG.
	if($output_format == IMAGETYPE_WEBP && !function_exists("imagewebp"))
	{
		$output_format = IMAGETYPE_PNG;
	}

	// Ensure that the file has the correct extension.
	$expected_extension = "";
	if($output_format == IMAGETYPE_JPEG)
	{
		$expected_extension = "jpg";
	}
	else if($output_format == IMAGETYPE_PNG)
	{
		$expected_extension = "png";
	}
	else if($output_format == IMAGETYPE_WEBP && function_exists("imagecreatefromwebp"))
	{
		$expected_extension = "webp";
	}
	else if($output_format == IMAGETYPE_GIF)
	{
		$expected_extension = "gif";
	}

	if($expected_extension) {
		$fileOut = preg_replace('/\.(jpe?g|png|gif|webp|JPE?G|PNG|GIF|WEBP)$/', '.' . $expected_extension, $fileOut);
	}

	// If the output file already exists, bail out to save resources.
	if(file_exists($fileOut))
	{
		return true;
	}

	// Get the current memory limit so that we can return it to its original value when we're done.
	$memory_limit = ini_get("memory_limit");
	ini_set("memory_limit", "512M");

	$folder = dirname($fileOut);
	if(!file_exists($folder))
	{
		@mkdir($folder, 0755, true);
	}

	if($orig_width == $reqWidth && $orig_height == $reqHeight && $type == $output_format)
	{
		// Image is already the required size. Bail out to avoid mutating JPGs.
		// Copy the file in to the expected thumbnail path to avoid breaking anything.
		copy($fileIn, $fileOut);
		return true;
	}

	$src_x = 0;
	$src_y = 0;
	$src_w = $orig_width;
	$src_h = $orig_height;

	// Check the EXIF data of the original file to see if it was rotated.
	if($type == IMAGETYPE_JPEG && function_exists("exif_read_data"))
	{
		$exif           = exif_read_data($fileIn);
		$orientation    = (isset($exif["Orientation"]) ? $exif["Orientation"] : 0);
	}
	else
	{
		$orientation    = 1;
	}

	$rotate = 0;

	if($orientation == 3 || $orientation == 4)
	{
		$rotate = 180;
	}
	else if($orientation == 5 || $orientation == 6)
	{
		$rotate = 90;
	}
	else if($orientation == 7 || $orientation == 8)
	{
		$rotate = 270;
	}

	if($crop !== false)
	{
		$exp        = explode(":", $crop);
		$w          = $exp[0];
		$h          = $exp[1];
		$crop_y     = (isset($exp[2]) ? $exp[2] : "center");
		$crop_x     = (isset($exp[3]) ? $exp[3] : "center");

		if($orig_width < $reqWidth)
		{
			$reqWidth = $orig_width;
		}
		if($orig_height < $reqHeight)
		{
			$reqHeight = $orig_height;
		}
		$ratio_w    = $orig_width / $reqWidth;
		$ratio_h    = $orig_height / $reqHeight;
		$src_ratio  = $orig_width / $orig_height;
		$req_ratio  = $reqWidth / $reqHeight;

		if($ratio_h >= $ratio_w) // && $height > $reqHeight)
		{
			$width  = $reqWidth;
			$height = ceil($reqWidth/$w*$h);

			if($src_ratio != $req_ratio)
			{
				$diff   = $orig_height - ($orig_width/$w*$h);

				if($crop_y == "top")
				{
					$src_y  = 0;
				}
				else // center
				{
					$src_y = floor($diff/2);
				}

				$src_h  = $orig_height - $diff;
			}
		}

		if($ratio_w > $ratio_h) // && $width > $reqWidth)
		{
			$width  = ceil($reqHeight/$h*$w);
			$height = $reqHeight;

			if($src_ratio != $req_ratio)
			{
				$diff   = $orig_width - ($orig_height/$h*$w);

				if($crop_x == "left")
				{
					$src_x  = 0;
				}
				else // center
				{
					$src_x = floor($diff/2);
				}

				$src_w  = $orig_width - $diff;
			}
		}
	}
	else
	{
		// Resize based on the width first.
		if(!empty($reqWidth) && $orig_width > $reqWidth)
		{
			$change = $reqWidth/$width;
			$width  = $reqWidth;
			$height = ceil($height * $change);
		}

		// Height is still too large, even after possibly resizing by width already.
		if(!empty($reqHeight) && $height > $reqHeight)
		{
			$change = $reqHeight/$height;
			$height = $reqHeight;
			$width  = ceil($width * $change);
		}
	}

	if($create_retina)
	{
		$scales = 2;
	}
	else
	{
		$scales = 1;
	}

	if($type == IMAGETYPE_JPEG)
	{
		$in = imagecreatefromjpeg($fileIn);
	}
	else if($type == IMAGETYPE_PNG)
	{
		$in = imagecreatefrompng($fileIn);
	}
	else if($type == IMAGETYPE_WEBP && function_exists("imagecreatefromwebp"))
	{
		$in = imagecreatefromwebp($fileIn);
	}
	else if($type == IMAGETYPE_GIF)
	{
		$in = imagecreatefromgif($fileIn);
	}

	// Rotate the source image and swap the width/height if necessary.
	if($rotate)
	{
		$in = imagerotate($in, $rotate*-1, 0);
	}

	for($scale=1; $scale<=$scales; $scale++)
	{
		$path = $fileOut;

		if($scale > 1)
		{
			$path = preg_replace('/\.(jpe?g|png|gif|webp|JPE?G|PNG|GIF|WEBP)$/', '@' . $scale . 'x.$1', $fileOut);
		}

		// Swap the width/height if the image is sideways.
		if($rotate == 90 || $rotate == 270)
		{
			$out = imagecreatetruecolor(($height * $scale), ($width * $scale));
		}
		else
		{
			$out = imagecreatetruecolor(($width * $scale), ($height * $scale));
		}

		if($output_format == IMAGETYPE_JPEG)
		{
			imageinterlace($out, true);
		}
		else if($output_format == IMAGETYPE_PNG)
		{
			imagecolortransparent($out, imagecolorallocatealpha($out, 0, 0, 0, 127));
			imagealphablending($out, false);
			imagesavealpha($out, true);
		}
		else if($output_format == IMAGETYPE_WEBP)
		{
			imagecolortransparent($out, imagecolorallocatealpha($out, 0, 0, 0, 127));
			imagealphablending($out, false);
			imagesavealpha($out, true);
		}
		else if($output_format == IMAGETYPE_GIF)
		{
			imagecolortransparent($out, imagecolorallocatealpha($out, 0, 0, 0, 127));
			imagealphablending($out, false);
			imagesavealpha($out, true);
			imageinterlace($out, true);
		}

		if($rotate == 90 || $rotate == 270)
		{
			imagecopyresampled($out, $in, 0, 0, $src_y, $src_x, ($height * $scale), ($width * $scale), $src_h, $src_w);
		}
		else
		{
			imagecopyresampled($out, $in, 0, 0, $src_x, $src_y, ($width * $scale), ($height * $scale), $src_w, $src_h);
		}

		if($output_format == IMAGETYPE_JPEG)
		{
			imagejpeg($out, $path, 85);
		}
		else if($output_format == IMAGETYPE_PNG)
		{
			imagepng($out, $path, 9, PNG_NO_FILTER);
		}
		else if($output_format == IMAGETYPE_WEBP)
		{
			imagewebp($out, $path, 85);
		}
		else if($output_format == IMAGETYPE_GIF)
		{
			imagegif($out, $path);
		}

		imagedestroy($out);
	}

	imagedestroy($in);

	// Return the memory limit to its original value.
	ini_set("memory_limit", $memory_limit);
}

function stripWhitespace($in, $out)
{
	$size = @getimagesize($in);
	$mime = $size["mime"];
	$jpg = ($mime == "image/jpeg");
	$png = ($mime == "image/png");

	//load the image
	if($jpg)
	{
		$img = imagecreatefromjpeg($in);
	}
	else if($png)
	{
		$img = imagecreatefrompng($in);
	}
	else
	{
		return false;
	}

	//find the size of the borders
	$b_top = 0;
	$b_btm = 0;
	$b_lft = 0;
	$b_rt = 0;

	//top
	for(; $b_top < imagesy($img); ++$b_top) {
	  for($x = 0; $x < imagesx($img); ++$x) {
		$colour = imagecolorat($img, $x, $b_top);
		$transparent = ($colour >> 24) & 0x7F;

		if(!$transparent) {
		   break 2; //out of the 'top' loop
		}
	  }
	}

	//bottom
	for(; $b_btm < imagesy($img); ++$b_btm) {
	  for($x = 0; $x < imagesx($img); ++$x) {
		$colour = imagecolorat($img, $x, imagesy($img) - $b_btm-1);
		$transparent = ($colour >> 24) & 0x7F;

		if(!$transparent) {
		   break 2; //out of the 'bottom' loop
		}
	  }
	}

	//left
	for(; $b_lft < imagesx($img); ++$b_lft) {
	  for($y = 0; $y < imagesy($img); ++$y) {
		$colour = imagecolorat($img, $b_lft, $y);
		$transparent = ($colour >> 24) & 0x7F;

		if(!$transparent) {
		   break 2; //out of the 'left' loop
		}
	  }
	}

	//right
	for(; $b_rt < imagesx($img); ++$b_rt) {
	  for($y = 0; $y < imagesy($img); ++$y) {
		$colour = imagecolorat($img, imagesx($img) - $b_rt-1, $y);
		$transparent = ($colour >> 24) & 0x7F;

		if(!$transparent) {
		   break 2; //out of the 'right' loop
		}
	  }
	}

	//copy the contents, excluding the border
	$newimg = imagecreatetruecolor(
		imagesx($img)-($b_lft+$b_rt), imagesy($img)-($b_top+$b_btm));

	imagecolortransparent($newimg, imagecolorallocate($newimg, 0, 0, 0));
	imagealphablending($newimg, false);
	imagesavealpha($newimg, true);
	imageinterlace($newimg, true);

	imagecopy($newimg, $img, 0, 0, $b_lft, $b_top, imagesx($newimg), imagesy($newimg));

	//finally, output the image
	//header("Content-Type: image/jpeg");
	if($jpg)
	{
		return imagejpeg($newimg, "$out", 100);
	}
	else if($png)
	{
		return imagepng($newimg, "$out", 9, PNG_NO_FILTER);
	}
}

function startsWith($haystack,$needle,$case=true) {
	if($case){return (strcmp(substr($haystack, 0, strlen($needle)),$needle)===0);}
	return (strcasecmp(substr($haystack, 0, strlen($needle)),$needle)===0);
}

function endsWith($haystack,$needle,$case=true) {
	if($case){return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)),$needle)===0);}
	return (strcasecmp(substr($haystack, strlen($haystack) - strlen($needle)),$needle)===0);
}

function check_url($url, &$info, $timeout = 5)
{
	$ch = curl_init(); // get cURL handle

	// set cURL options
	$opts = array(CURLOPT_RETURNTRANSFER 	=> true, 	// do not output to browser
				  CURLOPT_URL 				=> $url,	// set URL
				  CURLOPT_NOBODY 			=> true,	// do a HEAD request only
				  CURLOPT_TIMEOUT 			=> $timeout,// set timeout
				  CURLOPT_FOLLOWLOCATION 	=> true 	// follow redirects
				  );
	curl_setopt_array($ch, $opts);
	curl_exec($ch); // do it!

	$last_url 	= curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
	$retval 	= curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200; // check if HTTP OK
	$info 		= curl_getinfo($ch);

	curl_close($ch); // close handle

	return $retval;
}

function check_img($url, $timeout = 5)
{
	$ch = curl_init(); // get cURL handle

	// set cURL options
	$opts = array(CURLOPT_RETURNTRANSFER => true, // do not output to browser
							  CURLOPT_URL => $url,            // set URL
							  CURLOPT_NOBODY => true,                 // do a HEAD request only
							  CURLOPT_TIMEOUT => $timeout,			// set timeout
							  CURLOPT_FOLLOWLOCATION => true);
	curl_setopt_array($ch, $opts);

	curl_exec($ch); // do it!

	$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
	$final_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);

	//if we get redirected to a php file, this is a broken image.  cbsgroup.com does this to catch broken URLs
	$retval = $status_code == 200 && substr($final_url, -4, 4) != ".php"; // check if HTTP OK
	//echo(curl_getinfo($ch, CURLINFO_HTTP_CODE) . " from $url<br />");
	//fopen($link[1], "r")

	curl_close($ch); // close handle

	return $retval;
}

function add_marketing_template($message)
{
	$template = file_get_contents(ABSOLUTE_PATH . "/templates/marketing/marketing.html");
	$template = str_replace("[CONTENT]", $message, $template);

	return $template;
}

/**
 * Creates a full CKEditor with all buttons.
 * Our buttons for a full editor are defined in CKEditor's config.js and as such we're not going to redefine them here.
 * Its just a waste of bandwidth to redefine them here.
 */
function full_wysiwyg($id)
{
	?>
	<script>
		var nvFinderPath = "<?php echo(LIVE_SITE); ?>/finder";

		jQuery(window).on("load", function()
		{
			var editor = CKEDITOR.replace( '<?php echo($id); ?>',
			{
				filebrowserBrowseUrl : nvFinderPath,
				filebrowserNvimageBrowseUrl : nvFinderPath + '?type=images',
				filebrowserNvdocumentBrowseUrl : nvFinderPath + '?type=documents',
				filebrowserUploadUrl : nvFinderPath + '/quick_upload?type=files',
				filebrowserNvimageUploadUrl : nvFinderPath + '/quick_upload?type=images',
				filebrowserNvdocumentUploadUrl : nvFinderPath + '/quick_upload?type=documents',
				height: '600px'
			});
		});
	</script>
	<?php
}

function basic_wysiwyg($id)
{
	?>
	<script>
		var nvFinderPath = "<?php echo(LIVE_SITE); ?>/finder";

		jQuery(window).on("load", function()
		{
			var editor = CKEDITOR.replace( '<?php echo($id); ?>',
			{
				filebrowserBrowseUrl : nvFinderPath,
				filebrowserNvimageBrowseUrl : nvFinderPath + '?type=images',
				filebrowserNvdocumentBrowseUrl : nvFinderPath + '?type=documents',
				filebrowserUploadUrl : nvFinderPath + '/quick_upload?type=files',
				filebrowserNvimageUploadUrl : nvFinderPath + '/quick_upload?type=images',
				filebrowserNvdocumentUploadUrl : nvFinderPath + '/quick_upload?type=documents',
				height: '200px',
				toolbar: [
					{ name: 'document', items: [ 'Source', '-', 'RemoveFormat', '-', 'ShowBlocks' ] },
					{ name: 'insert', items: [ 'Image', 'Table', 'HorizontalRule' ] },
					{ name: 'links', items: [ 'Link', 'Unlink', 'Anchor' ] },
					{ name: 'clipboard', items: [ 'PasteText', 'PasteFromWord', '-', 'Scayt' ] },
					{ name: 'basicstyles', items: [ 'Bold', 'Italic', 'Underline' ] },
					{ name: 'paragraph', items: [ 'NumberedList', 'BulletedList', '-', 'Blockquote', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock' ] },
					{ name: 'styles', items: [ 'Styles', 'Format', 'FontSize' ] },
					{ name: 'colors', items: [ 'TextColor', 'BGColor' ] }
				]
			});
		});
	</script>
	<?php
}

function copy_directory($src, $dst)
{
	$dir = opendir($src);
	@mkdir($dst);

	while(false !== ($file = readdir($dir)))
	{
		if($file != '.' && $file != '..')
		{
			if(is_dir($src . '/' . $file))
			{
				copy_directory($src . '/' . $file,$dst . '/' . $file);
			}
			else
			{
				copy($src . '/' . $file,$dst . '/' . $file);
			}
		}
	}
	closedir($dir);
}

/*::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
/*::                                                                         :*/
/*::  This routine calculates the distance between two points (given the     :*/
/*::  latitude/longitude of those points). It is being used to calculate     :*/
/*::  the distance between two locations using GeoDataSource(TM) Products    :*/
/*::                                                                         :*/
/*::  Definitions:                                                           :*/
/*::    South latitudes are negative, east longitudes are positive           :*/
/*::                                                                         :*/
/*::  Passed to function:                                                    :*/
/*::    lat1, lon1 = Latitude and Longitude of point 1 (in decimal degrees)  :*/
/*::    lat2, lon2 = Latitude and Longitude of point 2 (in decimal degrees)  :*/
/*::    unit = the unit you desire for results                               :*/
/*::           where: 'M' is statute miles (default)                         :*/
/*::                  'K' is kilometers                                      :*/
/*::                  'N' is nautical miles                                  :*/
/*::  Worldwide cities and other features databases with latitude longitude  :*/
/*::  are available at http://www.geodatasource.com                          :*/
/*::                                                                         :*/
/*::  For enquiries, please contact sales@geodatasource.com                  :*/
/*::                                                                         :*/
/*::  Official Web site: http://www.geodatasource.com                        :*/
/*::                                                                         :*/
/*::         GeoDataSource.com (C) All Rights Reserved 2015                  :*/
/*::                                                                         :*/
/*::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
function distance($lat1, $lon1, $lat2, $lon2, $unit)
{
	$theta 	= $lon1 - $lon2;
	$dist 	= sin(deg2rad($lat1)) * sin(deg2rad($lat2)) +  cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
	$dist 	= acos($dist);
	$dist 	= rad2deg($dist);
	$miles 	= $dist * 60 * 1.1515;
	$unit 	= strtoupper($unit);

	if($unit == "K")
	{
		return ($miles * 1.609344);
	}
	else if($unit == "N")
	{
		return ($miles * 0.8684);
	}
	else
	{
		return $miles;
	}
}

if (!function_exists('curl_file_create'))
{
	function curl_file_create($filename, $mimetype = '', $postname = '')
	{
		return "@$filename;filename="
			. ($postname ?: basename($filename))
			. ($mimetype ? ";type=$mimetype" : '');
	}
}

function sort_dirs_array($a, $b)
{
	if ($a->value == $b->value)
	{
		return 0;
	}
	return ($a->value < $b->value) ? -1 : 1;
}

function bytes_format($bytes)
{
	if($bytes >= 1073741824)
	{
		return number_format($bytes/1024/1024/1024, 1) . " GB";
	}
	else if($bytes >= 1048576)
	{
		return number_format($bytes/1024/1024, 1) . " MB";
	}
	else if($bytes >= 1024)
	{
		return number_format($bytes/1024, 1) . " KB";
	}
	else
	{
		return number_format($bytes) . " B";
	}
}

/*function csrf()
{
	$CI = get_instance();
	$csrf = array(
		'name' => $CI->security->get_csrf_token_name(),
		'hash' => $CI->security->get_csrf_hash()
	);

	?><input type="hidden" name="<?php echo($csrf['name']); ?>" value="<?php echo($csrf['hash']); ?>" /><?php
}*/

/**
 * The built in Cross Site Request Forgery functions from CodeIgniter are all or nothing.
 * This breaks a lot of stuff so we're going to manually generate and verify our own encrypted time based token.
 */
function csrf()
{
	$CI 	= get_instance();
	$CI->load->library("encryption");
	$hash 	= $CI->encryption->encrypt(time());

	?><input type="hidden" name="csrf_token" value="<?php echo($hash); ?>" /><?php
}

function csrf_verify()
{
	$CI 		= get_instance();
	$CI->load->library("encryption");
	$timestamp 	= $CI->encryption->decrypt($CI->input->post("csrf_token"));

	if(!is_numeric($timestamp) || $timestamp < strtotime("-1 hour"))
	{
		show_error("A cross site request forgery token (CSRF) improves the security of your website. Your CSRF token was missing or was more than 1 hour old from the time you loaded the previous page.", 403, "Invalid CSRF Token");
	}
}

function server_basename()
{
	$CI = get_instance();
	$server_name = $CI->input->server("SERVER_NAME");

	if(!$server_name)
	{
		$server_name = $CI->input->server("HTTP_HOST");
	}

	return preg_replace("/^(dev|review|new|www)\./", "", $server_name);
}

/**
 * Returns an array of Font Awesome icons (v5.0.9 FREE).
 */
function get_icons()
{
	return array(
"fa-500px fab",
"fa-accessible-icon fab",
"fa-accusoft fab",
"fa-address-book far",
"fa-address-book fas",
"fa-address-card far",
"fa-address-card fas",
"fa-adjust fas",
"fa-adn fab",
"fa-adversal fab",
"fa-affiliatetheme fab",
"fa-algolia fab",
"fa-align-center fas",
"fa-align-justify fas",
"fa-align-left fas",
"fa-align-right fas",
"fa-allergies fas",
"fa-amazon fab",
"fa-amazon-pay fab",
"fa-ambulance fas",
"fa-american-sign-language-interpreting fas",
"fa-amilia fab",
"fa-anchor fas",
"fa-android fab",
"fa-angellist fab",
"fa-angle-double-down fas",
"fa-angle-double-left fas",
"fa-angle-double-right fas",
"fa-angle-double-up fas",
"fa-angle-down fas",
"fa-angle-left fas",
"fa-angle-right fas",
"fa-angle-up fas",
"fa-angrycreative fab",
"fa-angular fab",
"fa-app-store fab",
"fa-app-store-ios fab",
"fa-apper fab",
"fa-apple fab",
"fa-apple-pay fab",
"fa-archive fas",
"fa-arrow-alt-circle-down far",
"fa-arrow-alt-circle-down fas",
"fa-arrow-alt-circle-left far",
"fa-arrow-alt-circle-left fas",
"fa-arrow-alt-circle-right far",
"fa-arrow-alt-circle-right fas",
"fa-arrow-alt-circle-up far",
"fa-arrow-alt-circle-up fas",
"fa-arrow-circle-down fas",
"fa-arrow-circle-left fas",
"fa-arrow-circle-right fas",
"fa-arrow-circle-up fas",
"fa-arrow-down fas",
"fa-arrow-left fas",
"fa-arrow-right fas",
"fa-arrow-up fas",
"fa-arrows-alt fas",
"fa-arrows-alt-h fas",
"fa-arrows-alt-v fas",
"fa-assistive-listening-systems fas",
"fa-asterisk fas",
"fa-asymmetrik fab",
"fa-at fas",
"fa-audible fab",
"fa-audio-description fas",
"fa-autoprefixer fab",
"fa-avianex fab",
"fa-aviato fab",
"fa-aws fab",
"fa-backward fas",
"fa-balance-scale fas",
"fa-ban fas",
"fa-band-aid fas",
"fa-bandcamp fab",
"fa-barcode fas",
"fa-bars fas",
"fa-baseball-ball fas",
"fa-basketball-ball fas",
"fa-bath fas",
"fa-battery-empty fas",
"fa-battery-full fas",
"fa-battery-half fas",
"fa-battery-quarter fas",
"fa-battery-three-quarters fas",
"fa-bed fas",
"fa-beer fas",
"fa-behance fab",
"fa-behance-square fab",
"fa-bell far",
"fa-bell fas",
"fa-bell-slash far",
"fa-bell-slash fas",
"fa-bicycle fas",
"fa-bimobject fab",
"fa-binoculars fas",
"fa-birthday-cake fas",
"fa-bitbucket fab",
"fa-bitcoin fab",
"fa-bity fab",
"fa-black-tie fab",
"fa-blackberry fab",
"fa-blind fas",
"fa-blogger fab",
"fa-blogger-b fab",
"fa-bluetooth fab",
"fa-bluetooth-b fab",
"fa-bold fas",
"fa-bolt fas",
"fa-bomb fas",
"fa-book fas",
"fa-bookmark far",
"fa-bookmark fas",
"fa-bowling-ball fas",
"fa-box fas",
"fa-box-open fas",
"fa-boxes fas",
"fa-braille fas",
"fa-briefcase fas",
"fa-briefcase-medical fas",
"fa-btc fab",
"fa-bug fas",
"fa-building far",
"fa-building fas",
"fa-bullhorn fas",
"fa-bullseye fas",
"fa-burn fas",
"fa-buromobelexperte fab",
"fa-bus fas",
"fa-buysellads fab",
"fa-calculator fas",
"fa-calendar far",
"fa-calendar fas",
"fa-calendar-alt far",
"fa-calendar-alt fas",
"fa-calendar-check far",
"fa-calendar-check fas",
"fa-calendar-minus far",
"fa-calendar-minus fas",
"fa-calendar-plus far",
"fa-calendar-plus fas",
"fa-calendar-times far",
"fa-calendar-times fas",
"fa-camera fas",
"fa-camera-retro fas",
"fa-capsules fas",
"fa-car fas",
"fa-caret-down fas",
"fa-caret-left fas",
"fa-caret-right fas",
"fa-caret-square-down far",
"fa-caret-square-down fas",
"fa-caret-square-left far",
"fa-caret-square-left fas",
"fa-caret-square-right far",
"fa-caret-square-right fas",
"fa-caret-square-up far",
"fa-caret-square-up fas",
"fa-caret-up fas",
"fa-cart-arrow-down fas",
"fa-cart-plus fas",
"fa-cc-amazon-pay fab",
"fa-cc-amex fab",
"fa-cc-apple-pay fab",
"fa-cc-diners-club fab",
"fa-cc-discover fab",
"fa-cc-jcb fab",
"fa-cc-mastercard fab",
"fa-cc-paypal fab",
"fa-cc-stripe fab",
"fa-cc-visa fab",
"fa-centercode fab",
"fa-certificate fas",
"fa-chart-area fas",
"fa-chart-bar far",
"fa-chart-bar fas",
"fa-chart-line fas",
"fa-chart-pie fas",
"fa-check fas",
"fa-check-circle far",
"fa-check-circle fas",
"fa-check-square far",
"fa-check-square fas",
"fa-chess fas",
"fa-chess-bishop fas",
"fa-chess-board fas",
"fa-chess-king fas",
"fa-chess-knight fas",
"fa-chess-pawn fas",
"fa-chess-queen fas",
"fa-chess-rook fas",
"fa-chevron-circle-down fas",
"fa-chevron-circle-left fas",
"fa-chevron-circle-right fas",
"fa-chevron-circle-up fas",
"fa-chevron-down fas",
"fa-chevron-left fas",
"fa-chevron-right fas",
"fa-chevron-up fas",
"fa-child fas",
"fa-chrome fab",
"fa-circle far",
"fa-circle fas",
"fa-circle-notch fas",
"fa-clipboard far",
"fa-clipboard fas",
"fa-clipboard-check fas",
"fa-clipboard-list fas",
"fa-clock far",
"fa-clock fas",
"fa-clone far",
"fa-clone fas",
"fa-closed-captioning far",
"fa-closed-captioning fas",
"fa-cloud fas",
"fa-cloud-download-alt fas",
"fa-cloud-upload-alt fas",
"fa-cloudscale fab",
"fa-cloudsmith fab",
"fa-cloudversify fab",
"fa-code fas",
"fa-code-branch fas",
"fa-codepen fab",
"fa-codiepie fab",
"fa-coffee fas",
"fa-cog fas",
"fa-cogs fas",
"fa-columns fas",
"fa-comment far",
"fa-comment fas",
"fa-comment-alt far",
"fa-comment-alt fas",
"fa-comment-dots fas",
"fa-comment-slash fas",
"fa-comments far",
"fa-comments fas",
"fa-compass far",
"fa-compass fas",
"fa-compress fas",
"fa-connectdevelop fab",
"fa-contao fab",
"fa-copy far",
"fa-copy fas",
"fa-copyright far",
"fa-copyright fas",
"fa-couch fas",
"fa-cpanel fab",
"fa-creative-commons fab",
"fa-credit-card far",
"fa-credit-card fas",
"fa-crop fas",
"fa-crosshairs fas",
"fa-css3 fab",
"fa-css3-alt fab",
"fa-cube fas",
"fa-cubes fas",
"fa-cut fas",
"fa-cuttlefish fab",
"fa-d-and-d fab",
"fa-dashcube fab",
"fa-database fas",
"fa-deaf fas",
"fa-delicious fab",
"fa-deploydog fab",
"fa-deskpro fab",
"fa-desktop fas",
"fa-deviantart fab",
"fa-diagnoses fas",
"fa-digg fab",
"fa-digital-ocean fab",
"fa-discord fab",
"fa-discourse fab",
"fa-dna fas",
"fa-dochub fab",
"fa-docker fab",
"fa-dollar-sign fas",
"fa-dolly fas",
"fa-dolly-flatbed fas",
"fa-donate fas",
"fa-dot-circle far",
"fa-dot-circle fas",
"fa-dove fas",
"fa-download fas",
"fa-draft2digital fab",
"fa-dribbble fab",
"fa-dribbble-square fab",
"fa-dropbox fab",
"fa-drupal fab",
"fa-dyalog fab",
"fa-earlybirds fab",
"fa-edge fab",
"fa-edit far",
"fa-edit fas",
"fa-eject fas",
"fa-elementor fab",
"fa-ellipsis-h fas",
"fa-ellipsis-v fas",
"fa-ember fab",
"fa-empire fab",
"fa-envelope far",
"fa-envelope fas",
"fa-envelope-open far",
"fa-envelope-open fas",
"fa-envelope-square fas",
"fa-envira fab",
"fa-eraser fas",
"fa-erlang fab",
"fa-ethereum fab",
"fa-etsy fab",
"fa-euro-sign fas",
"fa-exchange-alt fas",
"fa-exclamation fas",
"fa-exclamation-circle fas",
"fa-exclamation-triangle fas",
"fa-expand fas",
"fa-expand-arrows-alt fas",
"fa-expeditedssl fab",
"fa-external-link-alt fas",
"fa-external-link-square-alt fas",
"fa-eye fas",
"fa-eye-dropper fas",
"fa-eye-slash far",
"fa-eye-slash fas",
"fa-facebook fab",
"fa-facebook-f fab",
"fa-facebook-messenger fab",
"fa-facebook-square fab",
"fa-fast-backward fas",
"fa-fast-forward fas",
"fa-fax fas",
"fa-female fas",
"fa-fighter-jet fas",
"fa-file far",
"fa-file fas",
"fa-file-alt far",
"fa-file-alt fas",
"fa-file-archive far",
"fa-file-archive fas",
"fa-file-audio far",
"fa-file-audio fas",
"fa-file-code far",
"fa-file-code fas",
"fa-file-excel far",
"fa-file-excel fas",
"fa-file-image far",
"fa-file-image fas",
"fa-file-medical fas",
"fa-file-medical-alt fas",
"fa-file-pdf far",
"fa-file-pdf fas",
"fa-file-powerpoint far",
"fa-file-powerpoint fas",
"fa-file-video far",
"fa-file-video fas",
"fa-file-word far",
"fa-file-word fas",
"fa-film fas",
"fa-filter fas",
"fa-fire fas",
"fa-fire-extinguisher fas",
"fa-firefox fab",
"fa-first-aid fas",
"fa-first-order fab",
"fa-firstdraft fab",
"fa-flag far",
"fa-flag fas",
"fa-flag-checkered fas",
"fa-flask fas",
"fa-flickr fab",
"fa-flipboard fab",
"fa-fly fab",
"fa-folder far",
"fa-folder fas",
"fa-folder-open far",
"fa-folder-open fas",
"fa-font fas",
"fa-font-awesome fab",
"fa-font-awesome-alt fab",
"fa-font-awesome-flag fab",
"fa-fonticons fab",
"fa-fonticons-fi fab",
"fa-football-ball fas",
"fa-fort-awesome fab",
"fa-fort-awesome-alt fab",
"fa-forumbee fab",
"fa-forward fas",
"fa-foursquare fab",
"fa-free-code-camp fab",
"fa-freebsd fab",
"fa-frown far",
"fa-frown fas",
"fa-futbol far",
"fa-futbol fas",
"fa-gamepad fas",
"fa-gavel fas",
"fa-gem far",
"fa-gem fas",
"fa-genderless fas",
"fa-get-pocket fab",
"fa-gg fab",
"fa-gg-circle fab",
"fa-gift fas",
"fa-git fab",
"fa-git-square fab",
"fa-github fab",
"fa-github-alt fab",
"fa-github-square fab",
"fa-gitkraken fab",
"fa-gitlab fab",
"fa-gitter fab",
"fa-glass-martini fas",
"fa-glide fab",
"fa-glide-g fab",
"fa-globe fas",
"fa-gofore fab",
"fa-golf-ball fas",
"fa-goodreads fab",
"fa-goodreads-g fab",
"fa-google fab",
"fa-google-drive fab",
"fa-google-play fab",
"fa-google-plus fab",
"fa-google-plus-g fab",
"fa-google-plus-square fab",
"fa-google-wallet fab",
"fa-graduation-cap fas",
"fa-gratipay fab",
"fa-grav fab",
"fa-gripfire fab",
"fa-grunt fab",
"fa-gulp fab",
"fa-h-square fas",
"fa-hacker-news fab",
"fa-hacker-news-square fab",
"fa-hand-holding fas",
"fa-hand-holding-heart fas",
"fa-hand-holding-usd fas",
"fa-hand-lizard far",
"fa-hand-lizard fas",
"fa-hand-paper far",
"fa-hand-paper fas",
"fa-hand-peace far",
"fa-hand-peace fas",
"fa-hand-point-down far",
"fa-hand-point-down fas",
"fa-hand-point-left far",
"fa-hand-point-left fas",
"fa-hand-point-right far",
"fa-hand-point-right fas",
"fa-hand-point-up far",
"fa-hand-point-up fas",
"fa-hand-pointer far",
"fa-hand-pointer fas",
"fa-hand-rock far",
"fa-hand-rock fas",
"fa-hand-scissors far",
"fa-hand-scissors fas",
"fa-hand-spock far",
"fa-hand-spock fas",
"fa-hands fas",
"fa-hands-helping fas",
"fa-handshake far",
"fa-handshake fas",
"fa-hashtag fas",
"fa-hdd far",
"fa-hdd fas",
"fa-heading fas",
"fa-headphones fas",
"fa-heart far",
"fa-heart fas",
"fa-heartbeat fas",
"fa-hips fab",
"fa-hire-a-helper fab",
"fa-history fas",
"fa-hockey-puck fas",
"fa-home fas",
"fa-hooli fab",
"fa-hospital far",
"fa-hospital fas",
"fa-hospital-alt fas",
"fa-hospital-symbol fas",
"fa-hotjar fab",
"fa-hourglass far",
"fa-hourglass fas",
"fa-hourglass-end fas",
"fa-hourglass-half fas",
"fa-hourglass-start fas",
"fa-houzz fab",
"fa-html5 fab",
"fa-hubspot fab",
"fa-i-cursor fas",
"fa-id-badge far",
"fa-id-badge fas",
"fa-id-card far",
"fa-id-card fas",
"fa-id-card-alt fas",
"fa-image far",
"fa-image fas",
"fa-images far",
"fa-images fas",
"fa-imdb fab",
"fa-inbox fas",
"fa-indent fas",
"fa-industry fas",
"fa-info fas",
"fa-info-circle fas",
"fa-instagram fab",
"fa-internet-explorer fab",
"fa-ioxhost fab",
"fa-italic fas",
"fa-itunes fab",
"fa-itunes-note fab",
"fa-jenkins fab",
"fa-joget fab",
"fa-joomla fab",
"fa-js fab",
"fa-js-square fab",
"fa-jsfiddle fab",
"fa-key fas",
"fa-keyboard far",
"fa-keyboard fas",
"fa-keycdn fab",
"fa-kickstarter fab",
"fa-kickstarter-k fab",
"fa-korvue fab",
"fa-language fas",
"fa-laptop fas",
"fa-laravel fab",
"fa-lastfm fab",
"fa-lastfm-square fab",
"fa-leaf fas",
"fa-leanpub fab",
"fa-lemon far",
"fa-lemon fas",
"fa-less fab",
"fa-level-down-alt fas",
"fa-level-up-alt fas",
"fa-life-ring far",
"fa-life-ring fas",
"fa-lightbulb far",
"fa-lightbulb fas",
"fa-line fab",
"fa-link fas",
"fa-linkedin fab",
"fa-linkedin-in fab",
"fa-linode fab",
"fa-linux fab",
"fa-lira-sign fas",
"fa-list fas",
"fa-list-alt far",
"fa-list-alt fas",
"fa-list-ol fas",
"fa-list-ul fas",
"fa-location-arrow fas",
"fa-lock fas",
"fa-lock-open fas",
"fa-long-arrow-alt-down fas",
"fa-long-arrow-alt-left fas",
"fa-long-arrow-alt-right fas",
"fa-long-arrow-alt-up fas",
"fa-low-vision fas",
"fa-lyft fab",
"fa-magento fab",
"fa-magic fas",
"fa-magnet fas",
"fa-male fas",
"fa-map far",
"fa-map fas",
"fa-map-marker fas",
"fa-map-marker-alt fas",
"fa-map-pin fas",
"fa-map-signs fas",
"fa-mars fas",
"fa-mars-double fas",
"fa-mars-stroke fas",
"fa-mars-stroke-h fas",
"fa-mars-stroke-v fas",
"fa-maxcdn fab",
"fa-medapps fab",
"fa-medium fab",
"fa-medium-m fab",
"fa-medkit fas",
"fa-medrt fab",
"fa-meetup fab",
"fa-meh far",
"fa-meh fas",
"fa-mercury fas",
"fa-microchip fas",
"fa-microphone fas",
"fa-microphone-slash fas",
"fa-microsoft fab",
"fa-minus fas",
"fa-minus-circle fas",
"fa-minus-square far",
"fa-minus-square fas",
"fa-mix fab",
"fa-mixcloud fab",
"fa-mizuni fab",
"fa-mobile fas",
"fa-mobile-alt fas",
"fa-modx fab",
"fa-monero fab",
"fa-money-bill-alt far",
"fa-money-bill-alt fas",
"fa-moon far",
"fa-moon fas",
"fa-motorcycle fas",
"fa-mouse-pointer fas",
"fa-music fas",
"fa-napster fab",
"fa-neuter fas",
"fa-newspaper far",
"fa-newspaper fas",
"fa-nintendo-switch fab",
"fa-node fab",
"fa-node-js fab",
"fa-notes-medical fas",
"fa-npm fab",
"fa-ns8 fab",
"fa-nutritionix fab",
"fa-object-group far",
"fa-object-group fas",
"fa-object-ungroup far",
"fa-object-ungroup fas",
"fa-odnoklassniki fab",
"fa-odnoklassniki-square fab",
"fa-opencart fab",
"fa-openid fab",
"fa-opera fab",
"fa-optin-monster fab",
"fa-osi fab",
"fa-outdent fas",
"fa-page4 fab",
"fa-pagelines fab",
"fa-paint-brush fas",
"fa-palfed fab",
"fa-pallet fas",
"fa-paper-plane far",
"fa-paper-plane fas",
"fa-paperclip fas",
"fa-parachute-box fas",
"fa-paragraph fas",
"fa-paste fas",
"fa-patreon fab",
"fa-pause fas",
"fa-pause-circle far",
"fa-pause-circle fas",
"fa-paw fas",
"fa-paypal fab",
"fa-pen-square fas",
"fa-pencil-alt fas",
"fa-people-carry fas",
"fa-percent fas",
"fa-periscope fab",
"fa-phabricator fab",
"fa-phoenix-framework fab",
"fa-phone fas",
"fa-phone-slash fas",
"fa-phone-square fas",
"fa-phone-volume fas",
"fa-php fab",
"fa-pied-piper fab",
"fa-pied-piper-alt fab",
"fa-pied-piper-pp fab",
"fa-piggy-bank fas",
"fa-pills fas",
"fa-pinterest fab",
"fa-pinterest-p fab",
"fa-pinterest-square fab",
"fa-plane fas",
"fa-play fas",
"fa-play-circle far",
"fa-play-circle fas",
"fa-playstation fab",
"fa-plug fas",
"fa-plus fas",
"fa-plus-circle fas",
"fa-plus-square far",
"fa-plus-square fas",
"fa-podcast fas",
"fa-poo fas",
"fa-pound-sign fas",
"fa-power-off fas",
"fa-prescription-bottle fas",
"fa-prescription-bottle-alt fas",
"fa-print fas",
"fa-procedures fas",
"fa-product-hunt fab",
"fa-pushed fab",
"fa-puzzle-piece fas",
"fa-python fab",
"fa-qq fab",
"fa-qrcode fas",
"fa-question fas",
"fa-question-circle far",
"fa-question-circle fas",
"fa-quidditch fas",
"fa-quinscape fab",
"fa-quora fab",
"fa-quote-left fas",
"fa-quote-right fas",
"fa-random fas",
"fa-ravelry fab",
"fa-react fab",
"fa-readme fab",
"fa-rebel fab",
"fa-recycle fas",
"fa-red-river fab",
"fa-reddit fab",
"fa-reddit-alien fab",
"fa-reddit-square fab",
"fa-redo fas",
"fa-redo-alt fas",
"fa-registered far",
"fa-registered fas",
"fa-rendact fab",
"fa-renren fab",
"fa-reply fas",
"fa-reply-all fas",
"fa-replyd fab",
"fa-resolving fab",
"fa-retweet fas",
"fa-ribbon fas",
"fa-road fas",
"fa-rocket fas",
"fa-rocketchat fab",
"fa-rockrms fab",
"fa-rss fas",
"fa-rss-square fas",
"fa-ruble-sign fas",
"fa-rupee-sign fas",
"fa-safari fab",
"fa-sass fab",
"fa-save far",
"fa-save fas",
"fa-schlix fab",
"fa-scribd fab",
"fa-search fas",
"fa-search-minus fas",
"fa-search-plus fas",
"fa-searchengin fab",
"fa-seedling fas",
"fa-sellcast fab",
"fa-sellsy fab",
"fa-server fas",
"fa-servicestack fab",
"fa-share fas",
"fa-share-alt fas",
"fa-share-alt-square fas",
"fa-share-square far",
"fa-share-square fas",
"fa-shekel-sign fas",
"fa-shield-alt fas",
"fa-ship fas",
"fa-shipping-fast fas",
"fa-shirtsinbulk fab",
"fa-shopping-bag fas",
"fa-shopping-basket fas",
"fa-shopping-cart fas",
"fa-shower fas",
"fa-sign fas",
"fa-sign-in-alt fas",
"fa-sign-language fas",
"fa-sign-out-alt fas",
"fa-signal fas",
"fa-simplybuilt fab",
"fa-sistrix fab",
"fa-sitemap fas",
"fa-skyatlas fab",
"fa-skype fab",
"fa-slack fab",
"fa-slack-hash fab",
"fa-sliders-h fas",
"fa-slideshare fab",
"fa-smile far",
"fa-smile fas",
"fa-smoking fas",
"fa-snapchat fab",
"fa-snapchat-ghost fab",
"fa-snapchat-square fab",
"fa-snowflake far",
"fa-snowflake fas",
"fa-sort fas",
"fa-sort-alpha-down fas",
"fa-sort-alpha-up fas",
"fa-sort-amount-down fas",
"fa-sort-amount-up fas",
"fa-sort-down fas",
"fa-sort-numeric-down fas",
"fa-sort-numeric-up fas",
"fa-sort-up fas",
"fa-soundcloud fab",
"fa-space-shuttle fas",
"fa-speakap fab",
"fa-spinner fas",
"fa-spotify fab",
"fa-square far",
"fa-square fas",
"fa-square-full fas",
"fa-stack-exchange fab",
"fa-stack-overflow fab",
"fa-star far",
"fa-star fas",
"fa-star-half far",
"fa-star-half fas",
"fa-staylinked fab",
"fa-steam fab",
"fa-steam-square fab",
"fa-steam-symbol fab",
"fa-step-backward fas",
"fa-step-forward fas",
"fa-stethoscope fas",
"fa-sticker-mule fab",
"fa-sticky-note far",
"fa-sticky-note fas",
"fa-stop fas",
"fa-stop-circle far",
"fa-stop-circle fas",
"fa-stopwatch fas",
"fa-strava fab",
"fa-street-view fas",
"fa-strikethrough fas",
"fa-stripe fab",
"fa-stripe-s fab",
"fa-studiovinari fab",
"fa-stumbleupon fab",
"fa-stumbleupon-circle fab",
"fa-subscript fas",
"fa-subway fas",
"fa-suitcase fas",
"fa-sun far",
"fa-sun fas",
"fa-superpowers fab",
"fa-superscript fas",
"fa-supple fab",
"fa-sync fas",
"fa-sync-alt fas",
"fa-syringe fas",
"fa-table fas",
"fa-table-tennis fas",
"fa-tablet fas",
"fa-tablet-alt fas",
"fa-tablets fas",
"fa-tachometer-alt fas",
"fa-tag fas",
"fa-tags fas",
"fa-tape fas",
"fa-tasks fas",
"fa-taxi fas",
"fa-telegram fab",
"fa-telegram-plane fab",
"fa-tencent-weibo fab",
"fa-terminal fas",
"fa-text-height fas",
"fa-text-width fas",
"fa-th fas",
"fa-th-large fas",
"fa-th-list fas",
"fa-themeisle fab",
"fa-thermometer fas",
"fa-thermometer-empty fas",
"fa-thermometer-full fas",
"fa-thermometer-half fas",
"fa-thermometer-quarter fas",
"fa-thermometer-three-quarters fas",
"fa-thumbs-down far",
"fa-thumbs-down fas",
"fa-thumbs-up far",
"fa-thumbs-up fas",
"fa-thumbtack fas",
"fa-ticket-alt fas",
"fa-times fas",
"fa-times-circle far",
"fa-times-circle fas",
"fa-tint fas",
"fa-toggle-off fas",
"fa-toggle-on fas",
"fa-trademark fas",
"fa-train fas",
"fa-transgender fas",
"fa-transgender-alt fas",
"fa-trash fas",
"fa-trash-alt far",
"fa-trash-alt fas",
"fa-tree fas",
"fa-trello fab",
"fa-tripadvisor fab",
"fa-trophy fas",
"fa-truck fas",
"fa-truck-loading fas",
"fa-truck-moving fas",
"fa-tty fas",
"fa-tumblr fab",
"fa-tumblr-square fab",
"fa-tv fas",
"fa-twitch fab",
"fa-twitter fab",
"fa-twitter-square fab",
"fa-typo3 fab",
"fa-uber fab",
"fa-uikit fab",
"fa-umbrella fas",
"fa-underline fas",
"fa-undo fas",
"fa-undo-alt fas",
"fa-uniregistry fab",
"fa-universal-access fas",
"fa-university fas",
"fa-unlink fas",
"fa-unlock fas",
"fa-unlock-alt fas",
"fa-untappd fab",
"fa-upload fas",
"fa-usb fab",
"fa-user far",
"fa-user fas",
"fa-user-circle far",
"fa-user-circle fas",
"fa-user-md fas",
"fa-user-plus fas",
"fa-user-secret fas",
"fa-user-times fas",
"fa-users fas",
"fa-ussunnah fab",
"fa-utensil-spoon fas",
"fa-utensils fas",
"fa-vaadin fab",
"fa-venus fas",
"fa-venus-double fas",
"fa-venus-mars fas",
"fa-viacoin fab",
"fa-viadeo fab",
"fa-viadeo-square fab",
"fa-vial fas",
"fa-vials fas",
"fa-viber fab",
"fa-video fas",
"fa-video-slash fas",
"fa-vimeo fab",
"fa-vimeo-square fab",
"fa-vimeo-v fab",
"fa-vine fab",
"fa-vk fab",
"fa-vnv fab",
"fa-volleyball-ball fas",
"fa-volume-down fas",
"fa-volume-off fas",
"fa-volume-up fas",
"fa-vuejs fab",
"fa-warehouse fas",
"fa-weibo fab",
"fa-weight fas",
"fa-weixin fab",
"fa-whatsapp fab",
"fa-whatsapp-square fab",
"fa-wheelchair fas",
"fa-whmcs fab",
"fa-wifi fas",
"fa-wikipedia-w fab",
"fa-window-close far",
"fa-window-close fas",
"fa-window-maximize far",
"fa-window-maximize fas",
"fa-window-minimize far",
"fa-window-minimize fas",
"fa-window-restore far",
"fa-window-restore fas",
"fa-windows fab",
"fa-wine-glass fas",
"fa-won-sign fas",
"fa-wordpress fab",
"fa-wordpress-simple fab",
"fa-wpbeginner fab",
"fa-wpexplorer fab",
"fa-wpforms fab",
"fa-wrench fas",
"fa-x-ray fas",
"fa-xbox fab",
"fa-xing fab",
"fa-xing-square fab",
"fa-y-combinator fab",
"fa-yahoo fab",
"fa-yandex fab",
"fa-yandex-international fab",
"fa-yelp fab",
"fa-yen-sign fas",
"fa-yoast fab",
"fa-youtube fab",
"fa-youtube-square fab"
		);
}
