<?php
/*======================================================================*\
|| #################################################################### ||
|| # ---------------------------------------------------------------- # ||
|| # Copyright ©2013 Fillip Hannisdal AKA Revan/NeoRevan/Belazor 	  # ||
|| # All Rights Reserved. 											  # ||
|| # This file may not be redistributed in whole or significant part. # ||
|| # ---------------------------------------------------------------- # ||
|| # You are not allowed to use this on your server unless the files  # ||
|| # you downloaded were done so with permission.					  # ||
|| # ---------------------------------------------------------------- # ||
|| #################################################################### ||
\*======================================================================*/

// #############################################################################
// vBMail functionality class

/**
* Handles everything to do with vBMail.
*/
class VBMAIL
{
	/**
	* Version info
	*
	* @public	mixed
	*/
	public static $jQueryVersion 	= '1.7.2';

	/**
	* The vBulletin registry object
	*
	* @private	vB_Registry
	*/
	protected static $vbulletin 	= NULL;

	/**
	* The database object
	*
	* @private	vBMail_Database
	*/
	public static $db 				= NULL;

	/**
	* The vBulletin registry object
	*
	* @private	vB_Registry
	*/
	protected static $prefix 		= 'dbtech_';

	/**
	* The vBulletin registry object
	*
	* @private	vB_Registry
	*/
	protected static $bitfieldgroup	= array(
		'vbmailpermissions'
	);

	/**
	* Array of permissions to be returned
	*
	* @public	array
	*/
	public static $permissions 		= NULL;

	/**
	* Array of cached items
	*
	* @public	array
	*/
	public static $cache			= array();

	/**
	* Whether we've called the DM fetcher
	*
	* @public	boolean
	*/
	protected static $called		= false;

	/**
	* Array of cached items
	*
	* @public	array
	*/
	public static $unserialize		= array(
		'mailinglist' => array(
			'permissions',
			'smtp',
		),
	);

	/**
	* Array of plugin globals
	*
	* @public	array
	*/
	public static $pluginglobals	= array();

	/**
	* Whether we have the pro version or not
	*
	* @public	boolean
	*/
	public static $isPro		= false;


	/**
	* Does important checking before anything else should be going on
	*
	* @param	vB_Registry	Registry object
	*/
	public static function init($vbulletin)
	{
		// Check if the vBulletin Registry is an object
		if (!is_object($vbulletin))
		{
			// Something went wrong here I think
			trigger_error("Registry object is not an object", E_USER_ERROR);
		}

		// Set registry
		self::$vbulletin =& $vbulletin;

		// Set database object
		self::$db = new vBMail_Database($vbulletin->db);

		// Set permissions shorthand
		self::_getPermissions();

		// What permissions to override
		$override = array(
			'canview',
		);

		foreach ($override as $permname)
		{
			// Override various permissions
			self::$permissions[$permname] = (self::$permissions['ismanager'] ? 1 : self::$permissions[$permname]);
		}

		foreach (self::$unserialize as $cachetype => $keys)
		{
			foreach ((array)self::$cache[$cachetype] as $id => $arr)
			{
				foreach ($keys as $key)
				{
					// Do unserialize
					self::$cache[$cachetype][$id][$key] = @unserialize($arr[$key]);
					self::$cache[$cachetype][$id][$key] = (is_array(self::$cache[$cachetype][$id][$key]) ? self::$cache[$cachetype][$id][$key] : array());
				}
			}
		}

		// Set pro version
		/*DBTECH_PRO_START*/
		self::$isPro = true;
		/*DBTECH_PRO_END*/
		
	}

	/**
	* Check if we have permissions to perform an action
	*
	* @param	array		User info
	* @param	array		Permissions info
	*/
	public static function checkPermissions(&$user, $permissions, $bitIndex)
	{
		if (!$user['usergroupid'] OR (!isset($user['membergroupids']) AND $user['userid']))
		{
			// Ensure we have this
			$user = fetch_userinfo($user['userid']);
		}

		if (!is_array($user['permissions']))
		{
			// Ensure we have the perms
			cache_permissions($user);
		}

		$ugs = fetch_membergroupids_array($user);
		if (!$ugs[0])
		{
			// Hardcode guests
			$ugs[0] = 1;
		}

		$bits = array(
			'product' 	=> 2,
			'coupon' 	=> 4
		);
		$bit = $bits[$bitIndex];

		//self::$vbulletin->usergroupcache
		foreach ($ugs as $usergroupid)
		{
			$value = $permissions[$usergroupid];
			$value = (isset($value) ? $value : -1);

			switch ($value)
			{
				case 1:
					// Allow
					return true;
					break;

				case -1:
					// Usergroup Default
					if (!($user[self::$prefix . self::$bitfieldgroup[0]] & $bit))
					{
						// Allow by default
						return true;
					}
					break;
			}
		}

		// We didn't make it
		return false;
	}

	/**
	* Class factory. This is used for instantiating the extended classes.
	*
	* @param	string			The type of the class to be called (user, forum etc.)
	* @param	vB_Registry		An instance of the vB_Registry object.
	* @param	integer			One of the ERRTYPE_x constants
	*
	* @return	vB_DataManager	An instance of the desired class
	*/
	public static function &initDataManager($classtype, &$registry, $errtype = ERRTYPE_STANDARD)
	{
		if (empty(self::$called))
		{
			// include the abstract base class
			require_once(DIR . '/includes/class_dm.php');
			self::$called = true;
		}

		if (preg_match('#^\w+$#', $classtype))
		{
			if (file_exists(DIR . '/dbtech/vbmail/includes/class_dm_' . strtolower($classtype) . '.php'))
			{
				// Lite
				require_once(DIR . '/dbtech/vbmail/includes/class_dm_' . strtolower($classtype) . '.php');
			}
			else
			{
				// Pro
				require_once(DIR . '/dbtech/vbmail_pro/includes/class_dm_' . strtolower($classtype) . '.php');
			}

			$classname = 'vBMail_DataManager_' . $classtype;
			$object = new $classname($registry, $errtype);

			return $object;
		}
	}

	/**
	* JS class fetcher for AdminCP
	*
	* @param	string	The JS file name or the code
	* @param	boolean	Whether it's a file or actual JS code
	*/
	public static function js($js = '', $file = true, $echo = true)
	{
		$output = '';
		if ($file)
		{
			$output = '<script type="text/javascript" src="' . self::$vbulletin->options['bburl'] . '/dbtech/vbmail/clientscript/vbmail' . $js . '.js?v=213pl1"></script>';
		}
		else
		{
			$output = "
				<script type=\"text/javascript\">
					<!--
					$js
					// -->
				</script>
			";
		}

		if ($echo)
		{
			echo $output;
		}
		else
		{
			return $output;
		}
	}

	/**
	* Determines the path to jQuery based on browser settings
	*/
	public static function jQueryPath()
	{
		// create the path to jQuery depending on the version
		if (self::$vbulletin->options['customjquery_path'])
		{
			$path = str_replace('{version}', self::$jQueryVersion, self::$vbulletin->options['customjquery_path']);
			if (!preg_match('#^https?://#si', self::$vbulletin->options['customjquery_path']))
			{
				$path = REQ_PROTOCOL . '://' . $path;
			}
			return $path;
		}
		else
		{
			switch (self::$vbulletin->options['remotejquery'])
			{
				case 1:
				default:
					// Google CDN
					return REQ_PROTOCOL . '://ajax.googleapis.com/ajax/libs/jquery/' . self::$jQueryVersion . '/jquery.min.js';
					break;

				case 2:
					// jQuery CDN
					return REQ_PROTOCOL . '://code.jquery.com/jquery-' . self::$jQueryVersion . '.min.js';
					break;

				case 3:
					// Microsoft CDN
					return REQ_PROTOCOL . '://ajax.aspnetcdn.com/ajax/jquery/jquery-' . self::$jQueryVersion . '.min.js';
					break;
			}
		}
	}

	/**
	* Returns a 'depth mark' for use in prefixing items that need to show depth in a hierarchy
	*
	* @param	integer	Depth of item (0 = no depth, 3 = third level depth)
	* @param	string	Character or string to repeat $depth times to build the depth mark
	* @param	string	Existing depth mark to append to
	*
	* @return	string
	*/
	function getDepthMark($depth, $depthchar, $depthmark = '')
	{
		for ($i = 0; $i < $depth; $i++)
		{
			$depthmark .= $depthchar;
		}
		return $depthmark;
	}

	/**
	* Breaks down a difference (in seconds) into its days / hours / minutes / seconds components.
	*
	* @param	integer	Difference (in seconds)
	*
	* @return	array
	*/
	function getTimeBreakdown($difference)
	{

		$breakdown = array();

		// Set days
		$breakdown['days'] = intval($difference / 86400);
		$difference -= ($breakdown['days'] * 86400);

		// Set hours
		$breakdown['hours'] = intval($difference / 3600);
		$difference -= ($breakdown['hours'] * 3600);

		// Set minutes
		$breakdown['minutes'] = intval($difference / 60);
		$difference -= ($breakdown['minutes'] * 60);

		// Set seconds
		$breakdown['seconds'] = intval($difference);

		return $breakdown;
	}

	/**
	* Quick Method of building the CPNav Template
	*
	* @param	string	The selected item in the CPNav
	*/
	public static function setNavClass($selectedcell = 'main')
	{
		global $navclass;

		$cells = array(
			'main',
			'mailinglists',
		);

		//($hook = vBulletinHook::fetch_hook('usercp_nav_start')) ? eval($hook) : false;

		// set the class for each cell/group
		$navclass = array();
		foreach ($cells AS $cellname)
		{
			$navclass[$cellname] = (intval(self::$vbulletin->versionnumber) == 3 ? 'alt2' : 'inactive');
		}
		$navclass[$selectedcell] = (intval(self::$vbulletin->versionnumber) == 3 ? 'alt1' : 'active');

		//($hook = vBulletinHook::fetch_hook('usercp_nav_complete')) ? eval($hook) : false;
	}

	/**
	* Escapes a string and makes it JavaScript-safe
	*
	* @param	mixed	The string or array to make JS-safe
	*/
	public static function jsEscapeString(&$arr)
	{
		$find = array(
			"\r\n",
			"\n",
			"\t",
			'"'
		);

		$replace = array(
			'\r\n',
			'\n',
			'\t',
			'\"',
		);

		$arr = str_replace($find, $replace, $arr);
	}

	/**
	* Encodes a string as a JSON object (consistent behaviour instead of relying on PHP built-in functions)
	*
	* @param	mixed	The string or array to encode
	* @param	boolean	(Optional) Whether this is an associative array
	* @param	boolean	(Optional) Whether we should escape the string or if they have already been escaped
	*/
	public static function encodeJSON($arr, $assoc = true, $doescape = true)
	{
		if ($doescape)
		{
			self::jsEscapeString($arr);
		}
		if (!$assoc)
		{
			// Not associative, simple return
			return '{"' . implode('","', $arr) . '"}';
		}

		$content = array();
		foreach ((array)$arr as $key => $val)
		{
			if (is_array($val))
			{
				// Recursion, definition: see recursion
				$val = self::encodeJSON($val);
				$content[] = '"' . $key . '":' . $val;
			}
			else
			{
				$content[] = '"' . $key . '":"' . $val . '"';
			}
		}

		return '{' . implode(',', $content) . '}';
	}

	/**
	* Outputs a JSON string to the browser
	*
	* @param	mixed	array to output
	*/
	/**
	* Outputs a JSON string to the browser
	*
	* @param	mixed	array to output
	*/
	public static function outputJSON($json, $full_shutdown = false)
	{
		if (headers_sent($file, $line))
		{
			die("Cannot send response, headers already sent. File: $file Line: $line");
		}

		// Store the charset
		$charset = strtoupper(self::getCharset());

		// We need to convert $json charset if we're not using UTF-8
		if ($charset != 'UTF-8')
		{
			$json = self::toCharset($json, $charset, 'UTF-8');
		}

		//If this is IE9, IE10, or IE11 -- we also need to work around the deliberate attempt to break "is IE" logic by the
		//IE dev team -- we need to send type "text/plain". Yes, we know that's not the standard.
		if (
			isset($_SERVER['HTTP_USER_AGENT']) && (
				(strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false) OR
				(strpos($_SERVER['HTTP_USER_AGENT'], 'Trident') !== false)
			)
		)
		{
			header('Content-type: text/plain; charset=UTF-8');
		}
		else
		{
			header('Content-type: application/json; charset=UTF-8');
		}

		// IE will cache ajax requests, and we need to prevent this - VBV-148
		header('Cache-Control: max-age=0,no-cache,no-store,post-check=0,pre-check=0');
		header('Expires: Sat, 1 Jan 2000 01:00:00 GMT');
		header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
		header("Pragma: no-cache");

		// Create JSON
		$json = self::encodeJSON($json);

		// Turn off debug output
		self::$vbulletin->debug = false;

		if (defined('VB_API') AND VB_API === true)
		{
			print_output($json);
		}

		//run any registered shutdown functions
		if (intval(self::$vbulletin->versionnumber) > 3)
		{
			$GLOBALS['vbulletin']->shutdown->shutdown();
		}
		exec_shut_down();
		self::$vbulletin->db->close();

		$sendHeader = false;
		switch(self::$vbulletin->options['ajaxheader'])
		{
			case 0 :
				$sendHeader = true;

			case 1 :
				$sendHeader = false;

			case 2 :
			default:
				$sendHeader = (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false);
		}

		if ($sendHeader)
		{
			// this line is causing problems with mod_gzip/deflate, but is needed for some IIS setups
			@header('Content-Length: ' . strlen($json));
		}

		// Finally spit out JSON
		echo $json;
		die();
	}

	/**
	 * Converts a string from one character encoding to another.
	 * If the target encoding is not specified then it will be resolved from the current
	 * language settings.
	 *
	 * @param	string|array	The string/array to convert
	 * @param	string	The source encoding
	 * @return	string	The target encoding
	 */
	public static function toCharset($in, $in_encoding, $target_encoding = false)
	{
		if (!$target_encoding) {
			if (!($target_encoding = self::getCharset())) {
				return $in;
			}
		}

		if (is_object($in))
		{
			foreach ($in as $key => $val)
			{
				$in->$key = self::toCharset($val, $in_encoding, $target_encoding);
			}

			return $in;
		}
		else if (is_array($in)) {
			foreach ($in as $key => $val)
			{
				$in["$key"] = self::toCharset($val, $in_encoding, $target_encoding);
			}

			return $in;
		}
		else if (is_string($in))
		{
			// ISO-8859-1 or other Western charset doesn't support Asian ones so that we need to NCR them
			// Iconv will ignore them
			if (preg_match("/^[ISO|Windows|IBM|MAC|CP]/i", $target_encoding)) {
				$in = self::ncrEncode($in, true, true);
			}

			// Try iconv
			if (function_exists('iconv')) {
				// Try iconv
				$out = @iconv($in_encoding, $target_encoding . '//IGNORE', $in);
				return $out;
			}

			// Try mbstring
			if (function_exists('mb_convert_encoding')) {
				return @mb_convert_encoding($in, $target_encoding, $in_encoding);
			}
		}
		else
		{
			// if it's not a string, array or object, don't modify it
			return $in;
		}
	}

	/**
	 * Gets the current charset
	 **/
	public static function getCharset()
	{
		static $lang_charset = '';
		if (!empty($lang_charset))
		{
			return $lang_charset;
		}

		if (intval(self::$vbulletin->versionnumber) > 3)
		{
			// vB4
			$lang_charset = vB_Template_Runtime::fetchStyleVar('charset');
		}
		else
		{
			// vB3
			$lang_charset = $GLOBALS['stylevar']['charset'];
		}

		if (!empty($lang_charset))
		{
			return $lang_charset;
		}

		$lang_charset = (!empty(self::$vbulletin->userinfo['lang_charset'])) ? self::$vbulletin->userinfo['lang_charset'] : 'utf-8';

		return $lang_charset;
	}

	/**
	* Converts a UTF-8 string into unicode NCR equivelants.
	*
	* @param	string	String to encode
	* @param	bool	Only ncrencode unicode bytes
	* @param	bool	If true and $skip_ascii is true, it will skip windows-1252 extended chars
	* @return	string	Encoded string
	*/
	public static function ncrEncode($str, $skip_ascii = false, $skip_win = false)
	{
		if (!$str)
		{
			return $str;
		}

		if (function_exists('mb_encode_numericentity'))
		{
			if ($skip_ascii)
			{
				if ($skip_win)
				{
					$start = 0xFE;
				}
				else
				{
					$start = 0x80;
				}
			}
			else
			{
				$start = 0x0;
			}
			return mb_encode_numericentity($str, array($start, 0xffff, 0, 0xffff), 'UTF-8');
		}

		if (is_pcre_unicode())
		{
			return preg_replace_callback(
				'#\X#u',
				create_function('$matches', 'return ncrencode_matches($matches, ' . (int)$skip_ascii . ', ' . (int)$skip_win . ');'),
				$str
			);
		}

		return $str;
	}

	/**
	* Constructs some <option>s for use in the templates
	*
	* @param	array	The key:value data array
	* @param	mixed	(Optional) The selected id(s)
	* @param	boolean	(Optional) Whether we should HTMLise the values
	*/
	public static function createSelectOptions($array, $selectedid = '', $htmlise = false)
	{
		if (!is_array($array))
		{
			return '';
		}

		$options = '';
		foreach ($array as $key => $val)
		{
			if (is_array($val))
			{
				// Create the template
				$templater = vB_Template::create('optgroup');
					$templater->register('optgroup_label', 	($htmlise ? htmlspecialchars_uni($key) : $key));
					$templater->register('optgroup_options', self::createSelectOptions($val, $selectedid, $tabindex, $htmlise));
				$options .= $templater->render();
			}
			else
			{
				if (is_array($selectedid))
				{
					$selected = iif(in_array($key, $selectedid), ' selected="selected"', '');
				}
				else
				{
					$selected = iif($key == $selectedid, ' selected="selected"', '');
				}

				$templater = vB_Template::create('option');
					$templater->register('optionvalue', 	($key !== 'no_value' ? $key : ''));
					$templater->register('optionselected', 	$selected);
					$templater->register('optiontitle', 	($htmlise ? htmlspecialchars_uni($val) : $val));
				$options .= $templater->render();
			}
		}

		return $options;
	}

	/**
	* Constructs a time selector
	*
	* @param	string	The title of the time select
	* @param	string	(Optional) The HTML form name
	* @param	array	(Optional) The time we should start with
	* @param	string	(Optional) The vertical align state
	*
	* @return	string	The constructed time row
	*/
	public static function timeRow($title, $name = 'date', $unixtime = '', $valign = 'middle')
	{
		global $vbphrase, $vbulletin;

		$output = '';

		$monthnames = array(
			0  => '- - - -',
			1  => $vbphrase['january'],
			2  => $vbphrase['february'],
			3  => $vbphrase['march'],
			4  => $vbphrase['april'],
			5  => $vbphrase['may'],
			6  => $vbphrase['june'],
			7  => $vbphrase['july'],
			8  => $vbphrase['august'],
			9  => $vbphrase['september'],
			10 => $vbphrase['october'],
			11 => $vbphrase['november'],
			12 => $vbphrase['december'],
		);

		if (is_array($unixtime))
		{
			require_once(DIR . '/includes/functions_misc.php');
			$unixtime = vbmktime(0, 0, 0, $unixtime['month'], $unixtime['day'], $unixtime['year']);
		}

		if ($unixtime)
		{
			$month = vbdate('n', $unixtime, false, false);
			$day = vbdate('j', $unixtime, false, false);
			$year = vbdate('Y', $unixtime, false, false);
			$hour = vbdate('G', $unixtime, false, false);
			$minute = vbdate('i', $unixtime, false, false);
		}

		$cell = array();
		$cell[] = "<label for=\"{$name}_month\">$vbphrase[month]</label><br /><select name=\"{$name}[month]\" id=\"{$name}_month\" tabindex=\"1\" class=\"primary select\"" . iif($vbulletin->debug, " title=\"name=&quot;$name" . "[month]&quot;\"") . ">\n" . self::createSelectOptions($monthnames, $month) . "\t\t</select>";
		$cell[] = "<label for=\"{$name}_date\">$vbphrase[day]</label><br /><input type=\"text\" class=\"primary textbox\" name=\"{$name}[day]\" id=\"{$name}_date\" value=\"$day\" size=\"4\" maxlength=\"2\" tabindex=\"1\"" . iif($vbulletin->debug, " title=\"name=&quot;$name" . "[day]&quot;\"") . ' />';
		$cell[] = "<label for=\"{$name}_year\">$vbphrase[year]</label><br /><input type=\"text\" class=\"primary textbox\" name=\"{$name}[year]\" id=\"{$name}_year\" value=\"$year\" size=\"4\" maxlength=\"4\" tabindex=\"1\"" . iif($vbulletin->debug, " title=\"name=&quot;$name" . "[year]&quot;\"") . ' />';
		$inputs = '';
		foreach($cell AS $html)
		{
			$inputs .= "\t\t<td style=\"padding-left:6px;\"><span class=\"smallfont\">$html</span></td>\n";
		}

		$output .= "<div id=\"ctrl_$name\" class=\"" . (intval(self::$vbulletin->versionnumber) == 3 ? 'alt1' : 'blockrow') . "\">$title: <table cellpadding=\"0\" cellspacing=\"2\" border=\"0\"><tr>\n$inputs\t\n</tr></table></div><br />";

		return $output;
	}

	/**
	* Sends a PM to a specified user
	*
	* @param	mixed	The UserID or userinfo to send the PM to
	* @param	string	Title of the PM
	* @param	string	Body of the PM
	* @param	mixed	Userinfo or vBOption key to send the PM from
	*/
	public static function sendPM($recipient, $title, $message, $sender = NULL)
	{
		global $vbphrase;

		if (!is_array($recipient))
		{
			// Who's the PM to
			$recipient = fetch_userinfo($recipient);
		}

		if (array_key_exists($sender, self::$vbulletin->options))
		{
			if (self::$vbulletin->options[$sender])
			{
				// Who's the PM from
				$sender = fetch_userinfo(self::$vbulletin->options[$sender]);
			}
			else
			{
				// Null this out since we had no defined sender
				$sender = NULL;
			}
		}

		if ($sender === NULL)
		{
			// We're using the recipient
			$sender = $recipient;
		}

		// Send pm
		$pmdm =& datamanager_init('PM', self::$vbulletin, ERRTYPE_ARRAY);
			$pmdm->set_info('is_automated', true); // implies overridequota
			$pmdm->set('fromuserid', 	$sender['userid']);
			$pmdm->set('fromusername', 	unhtmlspecialchars($sender['username']));
			$pmdm->set_recipients(unhtmlspecialchars($recipient['username']), $sender['permissions'], 'cc');
			$pmdm->setr('title', 		$title);
			$pmdm->setr('message', 		$message);
			$pmdm->set('dateline', 		TIMENOW);
			$pmdm->set('showsignature', 1);
			$pmdm->set('allowsmilie', 	1);
		if (!$pmdm->pre_save())
		{
			return $pmdm->errors;
		}
		else
		{
			return $pmdm->save();
		}
	}

	/**
	* Grabs what permissions we have got
	*/
	protected static function _getPermissions()
	{
		if (!self::$vbulletin->userinfo['permissions'])
		{
			// For some reason, this is missing
			cache_permissions(self::$vbulletin->userinfo);
		}

		foreach (self::$bitfieldgroup as $bitfieldgroup)
		{
			// Override bitfieldgroup variable
			$bitfieldgroup = self::$prefix . $bitfieldgroup;

			if (!is_array(self::$vbulletin->bf_ugp[$bitfieldgroup]))
			{
				// Something went wrong here I think
				require_once(DIR . '/includes/class_bitfield_builder.php');
				if (vB_Bitfield_Builder::build(false) !== false)
				{
					$myobj =& vB_Bitfield_Builder::init();
					if (sizeof($myobj->data['ugp'][$bitfieldgroup]) != sizeof(self::$vbulletin->bf_ugp[$bitfieldgroup]))
					{
						require_once(DIR . '/includes/adminfunctions.php');
						$myobj->save(self::$vbulletin->db);
						build_forum_permissions();

						if (IN_CONTROL_PANEL === true)
						{
							define('CP_REDIRECT', self::$vbulletin->scriptpath);
							print_stop_message('rebuilt_bitfields_successfully');
						}
						else
						{
							self::$vbulletin->url = self::$vbulletin->scriptpath;
							if (version_compare(self::$vbulletin->versionnumber, '4.1.7') >= 0)
							{
								eval(print_standard_redirect(array('redirect_updatethanks', self::$vbulletin->userinfo['username']), true, true));
							}
							else
							{
								eval(print_standard_redirect('redirect_updatethanks', true, true));
							}
						}
					}
				}
				else
				{
					echo "<strong>error</strong>\n";
					print_r(vB_Bitfield_Builder::fetch_errors());
					die();
				}
			}

			foreach ((array)self::$vbulletin->bf_ugp[$bitfieldgroup] as $permname => $bit)
			{
				// Set the permission
				self::$permissions[$permname] = (!$bit ? self::$vbulletin->userinfo['permissions'][$bitfieldgroup][$permname] : (self::$vbulletin->userinfo['permissions'][$bitfieldgroup] & $bit ? 1 : 0));
			}
		}
	}

	/**
	* Sets up the permissions based on mailinglist
	*
	* @param	array		The mailinglist
	* @param	array|null	User Info to check (null = vBulletin Userinfo)
	*/
	public static function load_mailinglist_permissions(&$mailinglist, $userinfo = NULL)
	{
		// Set permissions shorthand
		$permarray = array();

		// Ensure we can fetch bitfields
		require_once(DIR . '/includes/adminfunctions_options.php');
		$permissions = fetch_bitfield_definitions('nocache|dbtech_vbmailpermissions');

		if ($userinfo === NULL)
		{
			// We're using our own user info
			$userinfo = self::$vbulletin->userinfo;
		}
		else if ($userinfo['userid'] == self::$vbulletin->userinfo['userid'] AND is_array($mailinglist['permissions_parsed']))
		{
			// Just return parsed
			return $mailinglist['permissions_parsed'];
		}

		foreach (array_merge(array($userinfo['usergroupid']), explode(',', $userinfo['membergroupids'])) as $usergroupid)
		{
			if (!$usergroupid)
			{
				// Just skip it
				continue;
			}

			foreach ((array)$permissions as $permname => $bit)
			{
				if (!isset($permarray[$permname]))
				{
					// Default to false
					$permarray[$permname] = false;
				}

				if (!$permarray[$permname] AND ((int)$mailinglist['permissions'][$usergroupid] & (int)$bit))
				{
					// Override to true
					$permarray[$permname] = true;
				}
			}
		}

		// Some hardcoded ones
		//$permarray['isprotected'] 	= ((int)$userinfo['permissions']['dbtech_vbshoutpermissions'] & (int)self::$vbulletin->bf_ugp_dbtech_vbshoutpermissions['isprotected']);
		//$permarray['ismanager'] 	= ((int)$userinfo['permissions']['dbtech_vbshoutpermissions'] & (int)self::$vbulletin->bf_ugp_dbtech_vbshoutpermissions['ismanager']);
		//$permarray['canpm']			= (isset($permarray['canpm']) ? $permarray['canpm'] : 1);

		if ($userinfo == self::$vbulletin->userinfo)
		{
			// Set the completed permissions array
			$mailinglist['permissions_parsed'] = $permarray;
		}

		return $permarray;
	}

	/**
	* Creates a cryptographic key to use for determining if we're a valid unsubscriber
	*
	* @param	array		The user info - MUST CONTAIN USERID AND SALT!
	*/
	public static function create_subscriptionhash($user, $mailinglist)
	{
		return md5(md5(md5(($user['userid'] * $user['userid']) + $mailinglist['mailinglistid']) . $user['salt']));
	}

	/**
	* Subscribes a user to a specific mailing list, depending on various options
	*
	* @param	array		The user info - MUST CONTAIN USERID AND SALT!
	* @param	array		The mailing list info
	* @param	string		The type (used for the log)
	*/
	public static function subscribeUser($user, $mailinglist, $type = 'newuser', $instantSend = true)
	{
		global $show, $stylevar;

		$subscribeflag = 1;

		/*DBTECH_PRO_START*/
		if ($vbulletin->options['dbtech_vbmail_enable_confirmation'])
		{
			// We're not subscribed by default after all
			$subscribeflag = 0;

			// Set this so we know what we can use in mail_send hook
			self::$pluginglobals['mailinglist'] = $mailinglist;

			// Set unsubscribe hash
			$hash = self::create_subscriptionhash($user, $mailinglist);

			$unsubscribelink = self::$vbulletin->options['bburl'] . '/vbmail.php?do=mailinglists&amp;action=managesubscription&amp;mailinglistid=' . $mailinglist['mailinglistid'] . '&amp;u=' . $user['userid'] . '&amp;type=subscribed&subscriptionhash=' . $hash;
			$subscribelink = self::$vbulletin->options['bburl'] . '/vbmail.php?do=mailinglists&amp;action=managesubscription&amp;mailinglistid=' . $mailinglist['mailinglistid'] . '&amp;u=' . $user['userid'] . '&amp;type=subscribe&subscriptionhash=' . $hash;

			// Do some variable replacements
			$uniquesubject = str_replace(
				array('{userid}', '{username}', '{email}', '{mailinglist}', '{forumtitle}'),
				array($user['userid'], $user['username'], $user['email'], $mailinglist['title'], self::$vbulletin->options['bbtitle']),
				fetch_phrase('dbtech_vbmail_subscription_' . ($type == 'subscriptionlink' ? 'confirmation' : 'invitation') . '_title', 'dbtech_vbmail', '', false, false, $user['languageid'])
			);
			$uniquemessage = str_replace(
				array('{userid}', '{username}', '{email}', '{mailinglist}', '{forumtitle}', '{unsubscribelink}', '{subscribelink}'),
				array($user['userid'], $user['username'], $user['email'], $mailinglist['title'], self::$vbulletin->options['bbtitle'], '<a href="' . $unsubscribelink . '">' . fetch_phrase('dbtech_vbmail_unsubscribe', 'dbtech_vbmail', '', false, false, $user['languageid']) . '</a> ( ' . $unsubscribelink . ' )', '<a href="' . $subscribelink . '">' . fetch_phrase('dbtech_vbmail_subscribe', 'dbtech_vbmail', '', false, false, $user['languageid']) . '</a> ( ' . $subscribelink . ' )'),
				fetch_phrase('dbtech_vbmail_subscription_' . ($type == 'subscriptionlink' ? 'confirmation' : 'invitation') . '_message', 'dbtech_vbmail', '', false, false, $user['languageid'])
			);

			if ($show['vbmail_branding'] AND !$show['_dbtech_branding_override'])
			{
				$brandingVariables = array(
					'flavour' 			=> 'Mailing List provided by ',
					'productid' 		=> 107,
					'utm_source' 		=> str_replace('www.', '', htmlspecialchars_uni($_SERVER['HTTP_HOST'])) . ' (email)',
					'utm_content' 		=> (self::$isPro ? 'Pro' : 'Lite'),
					'referrerid' 		=> self::$vbulletin->options['dbtech_vbmail_referral'],
					'title' 			=> 'vBMail',
					'displayversion' 	=> self::$vbulletin->options['dbtech_vbmail_displayversion'],
					'version' 			=> '2.1.3 Patch Level 1',
					'producttype' 		=> $show['dbtech_vbmail_producttype']
				);
				$uniquemessage .= '<p style="font-size:11px;">' . $brandingVariables['flavour'] . '
					<a rel="nofollow" href="http://www.dragonbyte-tech.com/vbecommerce.php' . ($brandingVariables['productid'] ? '?productid=' . $brandingVariables['productid'] . '&do=product&' : '?') . 'utm_source=' . $brandingVariables['utm_source'] . '&utm_campaign=product&utm_medium=' . urlencode(str_replace(' ', '+', $brandingVariables['title'])) . '&utm_content=' . $brandingVariables['utm_content'] . ($brandingVariables['referrerid'] ? '&referrerid=' . $brandingVariables['referrerid'] : '') . '" target="_blank">' . $brandingVariables['title'] . '</a>.
					<a rel="nofollow" href="http://www.dragonbyte-tech.com/?utm_source=' . $brandingVariables['utm_source'] . '&utm_campaign=site&utm_medium=' . urlencode(str_replace(' ', '+', $brandingVariables['title'])) . '&utm_content=' . $brandingVariables['utm_content'] . ($brandingVariables['referrerid'] ? '&referrerid=' . $brandingVariables['referrerid'] : '') . '" target="_blank">Copyright &copy; ' . date('Y') . ' DragonByte Technologies Ltd.</a>
				</p>';
			}

			if (intval($vbulletin->versionnumber) > 3)
			{
				$stylevar['textdirection'] = vB_Template_Runtime::fetchStyleVar('textdirection');
				$stylevar['languagecode'] = vB_Template_Runtime::fetchStyleVar('languagecode');
			}

			$uniquemessage = '<html dir="' . $stylevar['textdirection'] . '" lang="' . $stylevar['languagecode'] . '">' . $uniquemessage . '</html>';

			// Fire ze email
			vbmail($user['email'], $uniquesubject, $uniquemessage, $instantSend, $mailinglist['fromemail']);

			// Get rid of this again
			unset(self::$pluginglobals['mailinglist']);
		}
		/*DBTECH_PRO_END*/

		self::$db->insertIgnore('dbtech_vbmail_subscription', array(
			'userid' 		=> $user['userid'],
			'mailinglistid' => $mailinglist['mailinglistid'],
			'subscribed' 	=> $subscribeflag
		));
		self::$db->insert('dbtech_vbmail_subscriptionlog', array(
			'userid' 		=> $user['userid'],
			'mailinglistid' => $mailinglist['mailinglistid'],
			'dateline' 		=> TIMENOW,
			'subscribed' 	=> $subscribeflag,
			'reason' 		=> $type . (!$subscribeflag ? '_confirmation' : '')
		));
	}
}

// #############################################################################
// database functionality class

/**
* Class that handles database wrapper
*/
class vBMail_Database
{
	/**
	* The vBulletin database object
	*
	* @private	vB_Database
	*/
	private $db;

	/**
	* The query result we executed
	*
	* @private	MySQL_Result
	*/
	private $result;

	/**
	* The query result we executed
	*
	* @private	MySQL_Result
	*/
	private $resultLoopable;

	/**
	* Whether we're debugging output
	*
	* @public	boolean
	*/
	public $debug = false;


	/**
	* Does important checking before anything else should be going on
	*
	* @param	vB_Registry		Registry object
	*/
	function __construct($dbobj)
	{
		$this->db = $dbobj;
	}

	/**
	 * Hides DB errrors
	 *
	 * @return void
	 */
	public function hideErrors()
	{
		$this->db->hide_errors();
	}

	/**
	 * Shows DB errrors
	 *
	 * @return void
	 */
	public function showErrors()
	{
		$this->db->show_errors();
	}

	/**
	 * Inserts a table row with specified data.
	 *
	 * @param mixed $table The table to insert data into.
	 * @param array $bind Column-value pairs.
	 * @param array $exclusions Array of field names that should be ignored from the $queryvalues array
	 * @param boolean $displayErrors Whether SQL errors should be displayed
	 * @param string $type Whether it's insert, insert ignore or replace
	 *
	 * @return int The number of affected rows.
	 */
	public function insert($table, array $bind, array $exclusions = array(), $displayErrors = true, $type = 'insert')
	{
		// Store the query
		$sql = fetch_query_sql($bind, $table, '', $exclusions);

		switch ($type)
		{
			case 'ignore':
				$sql = str_replace('INSERT INTO', 'INSERT IGNORE INTO', $sql);
				break;

			case 'replace':
				$sql = str_replace('INSERT INTO', 'REPLACE INTO', $sql);
				break;
		}

		if ($this->debug)
		{
			echo "<pre>";
			echo $sql;
			echo "</pre>";
			die();
		}

		if (!$displayErrors)
		{
			$this->db->hide_errors();
		}
		$this->db->query_write($sql);
		if (!$displayErrors)
		{
			$this->db->show_errors();
		}

		// Return insert ID if only one row was inserted, otherwise return number of affected rows
		$affected = $this->db->affected_rows();
		return(($affected === 1 AND $type == 'insert') ? $this->db->insert_id() : $affected);
	}

	/**
	 * Inserts a table row with specified data, ignoring duplicates.
	 *
	 * @param mixed $table The table to insert data into.
	 * @param array $bind Column-value pairs.
	 * @param array $exclusions Array of field names that should be ignored from the $queryvalues array
	 * @param boolean $displayErrors Whether SQL errors should be displayed
	 *
	 * @return int The number of affected rows.
	 */
	public function insertIgnore($table, array $bind, array $exclusions = array(), $displayErrors = true)
	{
		return $this->insert($table, $bind, $exclusions, $displayErrors, 'ignore');
	}

	/**
	 * Inserts a table row with specified data, replacing duplicates.
	 *
	 * @param mixed $table The table to insert data into.
	 * @param array $bind Column-value pairs.
	 * @param array $exclusions Array of field names that should be ignored from the $queryvalues array
	 * @param boolean $displayErrors Whether SQL errors should be displayed
	 *
	 * @return int The number of affected rows.
	 */
	public function replace($table, array $bind, array $exclusions = array(), $displayErrors = true)
	{
		return $this->insert($table, $bind, $exclusions, $displayErrors, 'replace');
	}

	/**
	 * Updates table rows with specified data based on a WHERE clause.
	 *
	 * @param  mixed		$table The table to update.
	 * @param  array		$bind  Column-value pairs.
	 * @param  mixed		$where UPDATE WHERE clause(s).
	 * @param  mixed		$exclusions Array of field names that should be ignored from the $queryvalues array
	 *
	 * @return int		  The number of affected rows.
	 */
	public function update($table, array $bind, $where, array $exclusions = array())
	{
		$sql = fetch_query_sql($bind, $table, $where, $exclusions);

		if ($this->debug)
		{
			echo "<pre>";
			echo $sql;
			echo "</pre>";
			die();
		}

		$this->db->query_write($sql);
		return $this->db->affected_rows();
	}

	/**
	 * Deletes table rows based on a WHERE clause.
	 *
	 * @param  mixed		$table The table to update.
	 * @param  mixed  		$bind Data to bind into DELETE placeholders.
	 * @param  mixed		$where DELETE WHERE clause(s).
	 *
	 * @return int		  The number of affected rows.
	 */
	public function delete($table, array $bind, $where = '')
	{
		/**
		 * Build the DELETE statement
		 */
		$sql = "DELETE FROM "
			 . TABLE_PREFIX . $table
			 . ' ' . $where;

		/**
		 * Execute the statement and return the number of affected rows
		 */
		$result = $this->query($sql, $bind, 'query_write');
		return $this->db->affected_rows();
	}

	/**
	 * Fetches all SQL result rows as a sequential array.
	 *
	 * @param string $sql  An SQL SELECT statement.
	 * @param mixed  $bind Data to bind into SELECT placeholders.
	 *
	 * @return array
	 */
	public function fetchAll($sql, $bind = array())
	{
		$results = array();

		$this->query($sql, $bind, 'query_read');
		while ($row = $this->db->fetch_array($this->result))
		{
			$results[] = $row;
		}
		return $results;
	}

	/**
	 * Fetches all SQL result rows and returns loopable object.
	 *
	 * @param string $sql  An SQL SELECT statement.
	 * @param mixed  $bind Data to bind into SELECT placeholders.
	 *
	 * @return array
	 */
	public function fetchAllObject($sql, $bind = array())
	{
		$this->resultLoopable = $this->query($sql, $bind, 'query_read');
		return $this->resultLoopable;
	}

	/**
	 * Fetches all SQL result rows and returns loopable object.
	 *
	 * @param string $sql  An SQL SELECT statement.
	 * @param mixed  $bind Data to bind into SELECT placeholders.
	 *
	 * @return array
	 */
	public function fetchCurrent()
	{
		return $this->db->fetch_array($this->resultLoopable);
	}

	/**
	 * Fetches results from the database with a specified column from each row keyed according to preference.
	 * The 'key' parameter provides the column name with which to key the result.
	 * The 'column' parameter provides the column name with which to use as the result.
	 * For example, calling fetchAllSingleKeyed('SELECT item_id, title, date FROM table', 'item_id', 'title')
	 * would result in an array keyed by item_id:
	 * [$itemId] => $title
	 *
	 * Note that the specified key must exist in the query result, or it will be ignored.
	 *
	 * @param string SQL to execute
	 * @param string Column with which to key the results array
	 * @param string Column to use as the result for that key
	 * @param mixed Parameters for the SQL
	 *
	 * @return array
	 */
	public function fetchAllSingleKeyed($sql, $key, $column, $bind = array())
	{
		$results = array();
		$i = 0;

		$this->query($sql, $bind, 'query_read');
		while ($row = $this->db->fetch_array($this->result))
		{
			$results[(isset($row[$key]) ? $row[$key] : $i)] = $row[$column];
			$i++;
		}

		return $results;
	}

	/**
	 * Fetches results from the database with each row keyed according to preference.
	 * The 'key' parameter provides the column name with which to key the result.
	 * For example, calling fetchAllKeyed('SELECT item_id, title, date FROM table', 'item_id')
	 * would result in an array keyed by item_id:
	 * [$itemId] => array('item_id' => $itemId, 'title' => $title, 'date' => $date)
	 *
	 * Note that the specified key must exist in the query result, or it will be ignored.
	 *
	 * @param string SQL to execute
	 * @param string Column with which to key the results array
	 * @param mixed Parameters for the SQL
	 *
	 * @return array
	 */
	public function fetchAllKeyed($sql, $key, $bind = array())
	{
		$results = array();
		$i = 0;

		$this->query($sql, $bind, 'query_read');
		while ($row = $this->db->fetch_array($this->result))
		{
			$results[(isset($row[$key]) ? $row[$key] : $i)] = $row;
			$i++;
		}

		return $results;
	}

	/**
	 * Fetches all SQL result rows as an associative array.
	 *
	 * The first column is the key, the entire row array is the
	 * value.  You should construct the query to be sure that
	 * the first column contains unique values, or else
	 * rows with duplicate values in the first column will
	 * overwrite previous data.
	 *
	 * @param string $sql An SQL SELECT statement.
	 * @param mixed $bind Data to bind into SELECT placeholders.
	 *
	 * @return array
	 */
	public function fetchAssoc($sql, $bind = array())
	{
		$data = array();
		$this->query($sql, $bind, 'query_read');
		while ($row = $this->db->fetch_array($this->result))
		{
			$key = key($row);
			$data[$row[$key]] = $row;
		}
		return $data;
	}

	/**
	 * Fetches the first row of the SQL result.
	 *
	 * @param string $sql An SQL SELECT statement.
	 * @param mixed  $bind Data to bind into SELECT placeholders.
	 * @param mixed  $fetchMode Override current fetch mode.
	 *
	 * @return array
	 */
	public function fetchRow($sql, $bind = array())
	{
		// Check the limit and fix $sql
		$limit = explode('limit', strtolower($sql));
		if (sizeof($limit) != 2 OR !is_numeric(trim($limit[1])))
		{
			// Append limit
			$sql .= ' LIMIT 1';
		}

		$result = $this->query($sql, $bind, 'query_first');
		return $result;
	}

	/**
	 * Fetches the first column of all SQL result rows as an array.
	 *
	 * @param string $sql An SQL SELECT statement.
	 * @param mixed  $bind Data to bind into SELECT placeholders.
	 * @param mixed  $column OPTIONAL - Key to use for the column index
	 * @return array
	 */
	public function fetchCol($sql, $bind = array(), $column = '')
	{
		$data = array();
		$this->query($sql, $bind, 'query_read');
		while ($row = $this->db->fetch_array($this->result))
		{
			// Validate the key
			$key = ((isset($row[$column]) AND $column) ? $column : key($row));
			$data[] = $row[$key];
		}
		return $data;
	}

	/**
	 * Fetches the first column of the first row of the SQL result.
	 *
	 * @param string $sql An SQL SELECT statement.
	 * @param mixed  $bind Data to bind into SELECT placeholders.
	 * @param mixed  $column OPTIONAL - Key to use for the column index
	 * @return string
	 */
	public function fetchOne($sql, $bind = array(), $column = '')
	{
		$result = $this->fetchRow($sql, $bind);
		return ($column ? $result[$column] : (is_array($result) ? reset($result) : ''));
	}

	/**
	 * Prepares and executes an SQL statement with bound data.
	 *
	 * @param  mixed  $sql  The SQL statement with placeholders.
	 * @param  mixed  $bind An array of data to bind to the placeholders.
	 * @param  string Which query method to use
	 *
	 * @return mixed  Result
	 */
	public function query($sql, $bind = array(), $which = 'query_read')
	{
		// make sure $bind is an array
		if (!is_array($bind))
		{
			$bind = (array)$bind;
		}

		if (!in_array($which, array('query_read', 'query_write', 'query_first')))
		{
			// Default to query read
			$which = 'query_read';
		}

		if (in_array($which, array('query_read', 'query_first')))
		{
			// Support slave servers
			$which .= '_slave';
		}

		foreach ($bind as $key => $val)
		{
			if (is_numeric($key))
			{
				// Sort string mapping
				$val = (is_numeric($val) ? "'$val'" : "'" . $this->db->escape_string($val) . "'");

				// Replace first instance of ?
				$sql = implode($val, explode('?', $sql, 2));
			}
		}

		foreach ($bind as $key => $val)
		{
			if (!is_numeric($key))
			{
				// Array of token replacements
				$sql = str_replace($key, $val, $sql);
			}
		}

		// Set the table prefix
		$sql = preg_replace('/\s+`?\$/U', ' ' . TABLE_PREFIX, $sql);

		if ($this->debug)
		{
			echo "<pre>";
			echo $sql;
			echo "</pre>";
			die();
		}

		// Execute the query
		$this->result = $this->db->$which($sql);
		return $this->result;
	}

	/**
	 * Helper function for IN statements for SQL queries.
	 * For example, with an array $userids = array(1, 2, 3, 4, 5);
	 * the query would be WHERE userid IN' . $this->queryList($userids) . '
	 *
	 * @param  array The array to work with
	 *
	 * @return mixed  Properly escaped and parenthesised IN() list
	 */
	public function queryList($arr)
	{
		$values = array();
		foreach ($arr as $val)
		{
			// Ensure the value is escaped properly
			$values[] = "'" . (is_numeric($val) ? $val : $this->db->escape_string($val)) . "'";
		}

		if (!count($values))
		{
			// Ensure there's no SQL errors
			$values[] = "'0'";
		}

		return 'IN(' . implode(', ', $values) . ')';
	}
}

// #############################################################################
// filter functionality class

/**
* Class that handles filtering arrays
*/
class VBMAIL_FILTER
{
	/**
	* Id Field we are using
	*
	* @private	string
	*/
	private static $idfield 	= NULL;

	/**
	* Id value we are looking for
	*
	* @private	mixed
	*/
	private static $idval 		= NULL;



	/**
	* Sets up and begins the filtering process
	*
	* @param	array	Array to filter
	* @param	string	What the ID Field is
	* @param	mixed	What we are looking for
	*
	* @return	array	Filtered array
	*/
	public static function filter($array, $idfield, $idval)
	{
		// Set the two things we can't pass on to the callback
		self::$idfield 	= $idfield;
		self::$idval	= $idval;

		// Filter this shiet
		return array_filter($array, array(__CLASS__, 'do_filter'));
	}

	/**
	* Checks if this element should be included
	*
	* @param	array	Array to filter
	*
	* @return	boolean	Whether we should include this or not
	*/
	protected static function do_filter($array)
	{
		$idfield 	= self::$idfield;
		$idval		= self::$idval;
		return ($array[$idfield] == $idval);
	}
}

if (!function_exists('is_pcre_unicode'))
{
// #############################################################################
/**
 * Checks if PCRE supports unicode
 *
 * @return bool
 */
function is_pcre_unicode()
{
	static $enabled;

	if (NULL !== $enabled)
	{
		return $enabled;
	}

	return $enabled = @preg_match('#\pN#u', '1');
}
}

if (!function_exists('ncrencode_matches'))
{
/**
 * NCR encodes matches from a preg_replace.
 * Single byte characters are preserved.
 *
 * @param	string	The character to encode
 * @return	string	The encoded character
 */
function ncrencode_matches($matches, $skip_ascii = false, $skip_win = false)
{
	$ord = ord_uni($matches[0]);

	if ($skip_win)
	{
		$start = 254;
	}
	else
	{
		$start = 128;
	}

	if ($skip_ascii AND $ord < $start)
	{
		return $matches[0];
	}

	return '&#' . ord_uni($matches[0]) . ';';
}
}

if (!function_exists('ord_uni'))
{
/**
 * Gets the Unicode Ordinal for a UTF-8 character.
 *
 * @param	string	Character to convert
 * @return	int		Ordinal value or false if invalid
 */
function ord_uni($chr)
{
	// Valid lengths and first byte ranges
	static $check_len = array(
		1 => array(0, 127),
		2 => array(192, 223),
		3 => array(224, 239),
		4 => array(240, 247),
		5 => array(248, 251),
		6 => array(252, 253)
	);

	// Get length
	$blen = strlen($chr);

	// Get single byte ordinals
	$b = array();
	for ($i = 0; $i < $blen; $i++)
	{
		$b[$i] = ord($chr[$i]);
	}

	// Check expected length
	foreach ($check_len AS $len => $range)
	{
		if (($b[0] >= $range[0]) AND ($b[0] <= $range[1]))
		{
			$elen = $len;
		}
	}

	// If no range found, or chr is too short then it's invalid
	if (!isset($elen) OR ($blen < $elen))
	{
		return false;
	}

	// Normalise based on octet-sequence length
	switch ($elen)
	{
		case (1):
			return $b[0];
		case (2):
			return ($b[0] - 192) * 64 + ($b[1] - 128);
		case (3):
			return ($b[0] - 224) * 4096 + ($b[1] - 128) * 64 + ($b[2] - 128);
		case (4):
			return ($b[0] - 240) * 262144 + ($b[1] - 128) * 4096 + ($b[2] - 128) * 64 + ($b[3] - 128);
		case (5):
			return ($b[0] - 248) * 16777216 + ($b[1] - 128) * 262144 + ($b[2] - 128) * 4096 + ($b[3] - 128) * 64 + ($b[4] - 128);
		case (6):
			return ($b[0] - 252) * 1073741824 + ($b[1] - 128) * 16777216 + ($b[2] - 128) * 262144 + ($b[3] - 128) * 4096 + ($b[4] - 128) * 64 + ($b[5] - 128);
	}
}
}